扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:Daniel Cazzulino 来源:Microsoft 开发人员网络 2007年10月26日
关键字: XML
某些自定义能够立即产生吸引力,因为它们是人们经常关心的与 xsd.exe 工具生成的类有关的问题。
大多数开发人员抱怨的问题之一是,xsd.exe 工具生成的类带有公共字段,而不是由私有字段支持的属性。XmlSerializer 生成的类通过使用常规的 [object].[member] 注释来读写该类的实例中的值。当然,从编译和源代码的角度来看,[member] 是字段还是属性没有什么区别。
因此借助于 CodeDom,可以更改 XSD 的默认类。由于自定义 codegen 工具中内置的可扩展性,需要做的所有工作只是实现一个新的 ICodeExtension。该扩展将处理 CodeDom 树中的每个类型,而无论它是一个类还是一个结构:
public class FieldsToPropertiesExtension : ICodeExtension { #region ICodeExtension Members public void Process( System.CodeDom.CodeNamespace code, System.Xml.Schema.XmlSchema schema ) { foreach ( CodeTypeDeclaration type in code.Types ) { if ( type.IsClass || type.IsStruct ) { // Turn fields to props
现在,我需要对该类型的每个成员(可能是字段、属性、方法等等)进行迭代,并且只处理 CodeMemberField 成员。不过,我不能只对 type.Members 集合执行 foreach 操作,因为对于每个字段而言,我都需要向同一集合中添加属性。这将导致发生异常,因为 foreach 结构所使用的基础枚举数可能会无效。因此,我需要将当前成员复制到某个数组中,然后改为对该数组进行迭代:
CodeTypeMember[] members = new CodeTypeMember[type.Members.Count]; type.Members.CopyTo( members, 0 ); foreach ( CodeTypeMember member in members ) { // Process fields only. if ( member is CodeMemberField ) { // Create property Next, I create the new property: CodeMemberProperty prop = new CodeMemberProperty(); prop.Name = member.Name; prop.Attributes = member.Attributes; prop.Type = ( ( CodeMemberField )member ).Type; // Copy attributes from field to the property. prop.CustomAttributes.AddRange( member.CustomAttributes ); member.CustomAttributes.Clear(); // Copy comments from field to the property. prop.Comments.AddRange( member.Comments ); member.Comments.Clear(); // Modify the field. member.Attributes = MemberAttributes.Private; Char[] letters = member.Name.ToCharArray(); letters[0] = Char.ToLower( letters[0] ); member.Name = String.Concat( "_", new string( letters ) );
请注意,我向新的属性中复制了字段名、它的成员特性以及类型。我将注释和自定义特性(XmlSerialization 特性)移出字段,然后移到属性(AddRange() 和 Clear())中。最后,我将该字段变为私有字段,并将其首字母转化为小写,在它前面加上“_”字符,这对于由属性支持的字段而言,是一种相当通用的命名规则。
但仍然缺少属性中最重要的元素:属性的 get 和 set 访问器的实现。因为它们只是对字段值进行传递,所以都非常简单:
prop.HasGet = true; prop.HasSet = true; // Add get/set statements pointing to field. Generates: // return this._fieldname; prop.GetStatements.Add( new CodeMethodReturnStatement( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), member.Name ) ) ); // Generates: // this._fieldname = value; prop.SetStatements.Add( new CodeAssignStatement( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), member.Name ), new CodeArgumentReferenceExpression( "value" ) ) );
最后,我们只需向该类型中添加新的属性:
type.Members.Add( prop ); }
好了,先前的架构通过该工具生成以下代码:
/// <remarks/> [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] public class Publisher { /// <remarks/> public string pub_id;
向该架构添加相应的扩展以后:
<xs:schema elementFormDefault="qualified" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:annotation> <xs:appinfo> <Code xmlns="http://weblogs.asp.net/cazzu"> <Extension Type="XsdGenerator.Extensions.FieldsToPropertiesExtension, XsdGenerator.CustomTool" /> </Code> </xs:appinfo> </xs:annotation> ...
该架构现在将生成:
/// [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)] public class Publisher { private string _pub_id; /// public string pub_id { get { return this._pub_id; } set { this._pub_id = value; } }
对于任何比较像样的读写(具有 get 和 set 属性)对象模型而言,要使其可供程序员方便地使用,它的多值属性都应该基于集合,而不是基于数组。这样做会使修改值和操纵对象图变得更为容易。通常的方法涉及到从 CollectionBase 派生一个新的类型化集合类。
在将更改提交给 CodeDom 之前,XmlSerializer 支持必须对集合进行检查。在分析和反射要序列化的类型的类的内部深处,有一个名为 TypeScope 的内部类。TypeScope 负责确保生成序列化代码。它包含一个有趣的方法,名为 ImportTypeDesc,该方法执行大多数检查工作并且为支持的类型生成信息。在这里,我们找到了对 IXmlSerializable(它检查其成员中的安全特性)、数组(必须具有等于 1 的秩)、Enums、XmlNode、XmlAttribute 和 XmlElement 等的特殊支持。
尤其是对集合而言,导入方法检查实现 ICollection 的类型,该类型必须满足下列规则:
必须具有一个 Add 方法,该方法不是由该接口定义的,因为它通常是为该集合将要容纳的专用类型而创建的。
不得通过该集合实现 IDictionary。
必须具有一个默认成员(即一个索引器)并且该成员具有一个类型为 System.Int32 (C# int) 的参数。系统将在所有类型层次结构中搜索这样的成员。
在 Add、Count 和索引器中不能有任何安全特性。
在验证上述信息以后,生成的派生自 XmlSerializationWriter 的专用类在为我们的类型编写 XML 输出时,将使用 Count 属性进行迭代,而不使用基于数组的属性的 Lenth:
MyAssembly.MyCollection a = (MyAssembly.MyCollection)o.@CollectionProperty;if (a != null) { for (int ia = 0; ia < a.Count; ia++) { Write10_MyCollectionItem(@"MyCollectionItem", @"http://weblogs.asp.net/cazzu/", ((MyAssembly.MyCollectionItem)a[ia]), false, false); }}
请注意,在给定对索引器的上一次检查之后,对集合和数组的索引访问是相同的,所以此处没有进行更改。
相应的派生自 XmlSerializationReader 的类使用类型化的 Add 方法来填充集合:
MyAssembly.MyCollection a_2 = (MyAssembly.MyCollection)o.@CollectionProperty; ... while (Reader.NodeType != System.Xml.XmlNodeType.EndElement) { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { if (((object) Reader.LocalName == (object)id8_MyCollectionItem && (object) Reader.NamespaceURI == (object)id9_httpweblogsaspnetcazzu)) { if ((object)(a_2) == null) Reader.Skip(); else a_2.Add(Read10_MyCollectionItem(false, true)); } ...
上面显示的读方法返回集合所期望的适当类型:
MyAssembly.MyCollectionItem Read1_MyCollectionItem(bool isNullable, bool checkType)
既然已经检验了 XmlSerializer 能够支持和正确处理基于集合的属性,那么将所有数组更改为相应的强类型集合就是安全的。
可以将这一新的扩展设计为在上一个扩展之前或之后运行。其中的差别是明显的,因为迭代将分别从字段更改到新的属性。为了使该扩展独立于上一个扩展,我将对其进行编码以针对字段工作。不过,请注意,如果将其配置为在 FieldsToPropertiesExtension“之后”运行,则该代码将是不正确的。
让我们首先分析将生成自定义集合的方法。该集合应如下所示:
public class PublisherCollection : CollectionBase { public int Add(Publisher value) { return base.InnerList.Add(value); } public Publisher this[int idx] { get { return (Publisher) base.InnerList[idx]; } set { base.InnerList[idx] = value; } } }
用于生成该类型化集合的代码为:
public CodeTypeDeclaration GetCollection( CodeTypeReference forType ) { CodeTypeDeclaration col = new CodeTypeDeclaration( forType.BaseType + "Collection" ); col.BaseTypes.Add(typeof(CollectionBase)); col.Attributes = MemberAttributes.Final | MemberAttributes.Public; // Add method CodeMemberMethod add = new CodeMemberMethod(); add.Attributes = MemberAttributes.Final | MemberAttributes.Public; add.Name = "Add"; add.ReturnType = new CodeTypeReference(typeof(int)); add.Parameters.Add( new CodeParameterDeclarationExpression ( forType, "value" ) ); // Generates: return base.InnerList.Add(value); add.Statements.Add( new CodeMethodReturnStatement ( new CodeMethodInvokeExpression( new CodePropertyReferenceExpression( new CodeBaseReferenceExpression(), "InnerList"), "Add", new CodeExpression[] { new CodeArgumentReferenceExpression( "value" ) } ) ) ); // Add to type. col.Members.Add(add); // Indexer property ('this') CodeMemberProperty indexer = new CodeMemberProperty(); indexer.Attributes = MemberAttributes.Final | MemberAttributes.Public; indexer.Name = "Item"; indexer.Type = forType; indexer.Parameters.Add( new CodeParameterDeclarationExpression ( typeof( int ), "idx" ) ); indexer.HasGet = true; indexer.HasSet = true; // Generates: return (theType) base.InnerList[idx]; indexer.GetStatements.Add( new CodeMethodReturnStatement ( new CodeCastExpression( forType, new CodeIndexerExpression( new CodePropertyReferenceExpression( new CodeBaseReferenceExpression(), "InnerList"), new CodeExpression[] { new CodeArgumentReferenceExpression( "idx" ) } ) ) ) ); // Generates: base.InnerList[idx] = value; indexer.SetStatements.Add( new CodeAssignStatement( new CodeIndexerExpression( new CodePropertyReferenceExpression( new CodeBaseReferenceExpression(), "InnerList"), new CodeExpression[] { new CodeArgumentReferenceExpression("idx") }), new CodeArgumentReferenceExpression( "value" ) ) ); // Add to type. col.Members.Add(indexer); return col; }
此时,您应该考虑一个在对 CodeDom 进行编程时有用的技巧;看到这些似乎没完没了的 Statements.Add 代码行了吗?当然,我们可以将它们拆分为多个独立的行,每行创建一个临时变量以容纳该对象并将其传递给下一行。但这样只会使它们更加无穷无尽!那好,只要您能够习惯,那么下面的技巧会是一种将这些代码行拆分为多个部分的好方法:
要生成 CodeDom 嵌套语句,邻近的属性 / 索引器 / 方法访问通常是从右向左构建的。
实际上:要生成以下代码行:
base.InnerList[idx]
您应该从索引器表达式 [idx] 开始,接着是属性访问 InnerList,最后是对象引用基。这将生成下面的 CodeDom 嵌套语句:
CodeExpression st = new CodeIndexerExpression( new CodePropertyReferenceExpression( new CodeBaseReferenceExpression(), "InnerList" ), new CodeExpression[] { new CodeArgumentReferenceExpression( "idx" ) } );
请注意,我从右向左创建语句,最后才完成适当的构造函数参数。用这种方式手动缩进和拆分代码行通常是一个好主意,这样可以更容易地看到各个对象构造函数在哪里结束以及哪些是它的参数。
最后,ICodeExtension.Process 方法实现涉及到对类型及其字段进行迭代,以查找基于数组的字段:
public class ArraysToCollectionsExtension : ICodeExtension { public void Process( CodeNamespace code, XmlSchema schema ) { // Copy as we will be adding types. CodeTypeDeclaration[] types = new CodeTypeDeclaration[code.Types.Count]; code.Types.CopyTo( types, 0 ); foreach ( CodeTypeDeclaration type in types ) { if ( type.IsClass || type.IsStruct ) { foreach ( CodeTypeMember member in type.Members ) { // Process fields only. if ( member is CodeMemberField && ( ( CodeMemberField )member ).Type.ArrayElementType != null ) { CodeMemberField field = ( CodeMemberField ) member; CodeTypeDeclaration col = GetCollection( field.Type.ArrayElementType ); // Change field type to collection. field.Type = new CodeTypeReference( col.Name ); code.Types.Add( col ); } } } } }
正像我在前面所做的,我复制了需要修改的集合;在此例中,是 CodeNamespace.Types。
进一步的自定义可以包括:向生成的类中添加 [Serializable],添加 DAL 方法(即 LoadById、FindByKey、Save、Delete 等),生成被序列化操作忽略但由您的代码使用的成员(应用 XmlIgnoreAttribute),省略属于外部导入架构的类的生成,等等。
查看本文来源如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者