.NET 框架中的 XML:在 .NET 框架中使用 XML 架构执行代码生成(3)

来源:百度文库 编辑:神马文学网 时间:2024/04/29 17:38:15
请注意,在给定对索引器的上一次检查之后,对集合和数组的索引访问是相同的,所以此处没有进行更改。
相应的派生自 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),省略属于外部导入架构的类的生成,等等。
返回页首
映射技巧
如果您要更深入地研究代码生成工具本身,或者希望更进一步地自定义架构处理,那么您或许会对下列与 codegen 类有关的高级问题感兴趣。如果您只是要开发扩展和操纵 CodeDom,则它们对您不会有多大价值,您可以跳过本部分而不会有任何问题。
我已经通过检索元素的 XmlTypeMapping 来处理这些元素;我尚未使用其任何属性,但如果您必须要找到与元素对应的 CodeTypeDeclaration,则可能需要使用这些属性。有关 XmlTypeMapping 属性及其含义的简短说明,请参阅 MSDN 文档。但是,该类用在许多方案中,如该文档中所示的 SoapReflectionImporter 映射导入。至于我所使用的 XmlSchemaImporter,我已经发现 XmlTypeMapping.TypeFullName 和 XmlTypeMapping.TypeName 对一个特定架构元素的设计具有不正确的行为:如果该元素在某个序列内部包含单个未绑定的子元素,则两者都将错误地假定子属性的类型。
因此,对于以下架构元素:

XmlTypeMapping.TypeFullName 和 XmlTypeMapping.TypeName 都没有“pubs”值(这是将要生成的类型),而是具有值“Publisher[]”,这是其唯一属性的类型。如果该序列具有一个以上的元素,则一切都可以按预期方式工作。请注意,无论元素的类型是否为命名的全局类型,或者无论该元素本身是否为引用,这一(明显的)错误都适用。
除了类型映射以外,XmlSchemaImporter 还可以检索将应用于其成员(字段)的映射。这很有用,因为 XSD/CLR 类型映射(包括 XSD 自定义派生类型)将被解析,并且您可以确信它就是由 XmlSerializer 使用的那个映射。您可以按如下方式获得成员映射:
XmlMembersMapping mmap = importer.ImportMembersMapping(
element.QualifiedName );
int count = mmap.Count;
for (int i = 0; i < count; i++)
{
XmlMemberMapping map = mmap[i];
//You have now:
// map.ElementName
// map.MemberName
// map.TypeFullName
// map.TypeName
}
XmlMemberMapping.TypeFullName 容纳命名空间限定的 CLR 类型,尽管 XmlMemberMapping.TypeName 具有 XSD 类型名。例如,对于 XSD 类型“xs:positiveInteger”的成员,前者将是“System.String”,而后者将是“positiveInteger”。如果您没有访问该成员映射检索的权限,则必须知道 XmlSerializer 所使用的所有 XSD 到 CLR 类型转换规则。请注意,这些规则不必与用于 XSD 验证和 DOM PSVI 的规则相同。
对于成员导入,有一个重要的警告(同样,明显是一个错误)。您不能重用 XmlSchemaImporter,否则将得到由导入代码在 XmlMembersMapping 构建时引发的 InvalidCastException。这可以通过每次使用导入程序的新实例来加以解决。
有了这些信息,您可以彻底更改类的外观,例如,重命名属性以使首字母变成大写,而不会对序列化基础结构产生危害。
当我讨论 codegen 类的基本原理时,我说过您只能为全局定义的元素检索(导入)映射;如果您创建自己的自定义特性以修改得到的类,则将只能够针对顶级元素检索和分析它们,因为您将只具有这些元素的映射。例如,假设您添加了一个 code:className 特性,该特性被某个扩展用来更改生成的类名:

您将能够为 pubs 元素检索这些映射,但无法为 publishers 子元素检索这些映射。因此,对其进行处理将是不安全的,因为 codegen 类将来可能发生更改。如果不能控制映射,您就不能简单地假设相应的 CodeTypeDeclaration 将具有与该元素相同的名称(以便找到和更改它)。当然,您可以自行决定是否可以接受这种危险。
返回页首
小结
通过重用为 XmlSerializer 创建的内置代码生成功能,可以确保对所生成的代码进行少量更改不会破坏 XML 序列化。通过 CodeDom 直接操纵其输出也可以增加灵活性。我在本文中说明了如何通过灵活处理 XML 架构来实现任意扩展以改变输出,并且开发了一些有用的示例。
在奠定这一牢固的基础之后,您可以继续研究更高级的方案,例如:外部(导入的/包含的)XSD 架构及其与代码生成的关系,操纵代码输出以重新使用应用程序或企业范围的储备库中的类型定义(包括 XSD 和相应的生成 .NET 类型),等等。
希望本文能够推动您使用新颖的方法来进行 XSD 存储和管理,以及相应的代码生成和重用。