XmlCodeExporter 和可为空的类型
Posted
技术标签:
【中文标题】XmlCodeExporter 和可为空的类型【英文标题】:XmlCodeExporter and nullable types 【发布时间】:2017-07-06 19:34:58 【问题描述】:System.Xml.Serialization.XmlCodeExporter
从 XSD 模式生成代码(以代码 CodeDom 形式)。但它有一些怪癖。例如一个可选元素:
<xs:element name="Something" type="xs:decimal" minOccurs="0" maxOccurs="1"/>
我希望这会生成一个相应的Nullable<decimal>
类型的代码成员,但它实际上创建了一个decimal
类型的成员,然后是一个单独的SomethingSpecified
字段,该字段应该单独切换以指示空值。这可能是因为该库是在引入可空类型之前开始的,但它导致代码非常不方便。
是否可以调整此代码生成,或者在这种情况下是否有替代工具可以生成更好的代码?
编辑:我知道我可以修改架构并添加 nillable='true'
,但我不想更改架构以解决代码生成的限制。
【问题讨论】:
试试 Xsd2Code 工具。我广泛使用它而不是 xsd.exe。不幸的是,我无法确定您提到的那个特定问题是否在那里得到了解决。 您是否尝试过使用nillable='true'
代替 min/max 出现?
@Evk:Xsd2Code 似乎在后台使用 XmlCodeExporter 并且似乎有同样的问题。
@tchrikch:是的,在架构中使用 nillable="true" 会生成可为空的属性,但我不想更改架构以解决代码生成中的限制,因为架构是共享合约。
@JacquesB 是的,我猜在最坏的情况下,您可以在解析之前预处理 xsd 模式,并在可行的情况下将 max/min 定义替换为 nillable
【参考方案1】:
Mike Hadlow 的文章
Writing your own XSD.exe
提供了创建您自己的 xsd.exe
版本的基本框架。它有以下步骤:
使用XmlSchema.Read()
和XmlSchemaImporter
导入架构。
使用XmlCodeExporter
生成要创建的.Net类型和属性。
根据需要调整生成的类型和属性。
在这里,您需要删除生成的 xxxSpecified
属性并将其对应的“真实”属性提升为可为空。
使用CSharpCodeProvider
生成最终代码。
使用这个框架,并通过使用调试器通过实验确定XmlCodeExporter
实际生成的类型,我创建了以下CustomXsdCodeGenerator
:
public class CustomXsdCodeGenerator : CustomXsdCodeGeneratorBase
readonly bool promoteToNullable;
public CustomXsdCodeGenerator(string Namespace, bool promoteToNullable) : base(Namespace)
this.promoteToNullable = promoteToNullable;
protected override void ModifyGeneratedCodeTypeDeclaration(CodeTypeDeclaration codeType, CodeNamespace codeNamespace)
RemoveSpecifiedProperties(codeNamespace, promoteToNullable);
base.ModifyGeneratedCodeTypeDeclaration(codeType, codeNamespace);
private static void RemoveSpecifiedProperties(CodeNamespace codeNamespace, bool promoteToNullable)
foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
RemoveSpecifiedProperties(codeType, codeNamespace, promoteToNullable);
private static void RemoveSpecifiedProperties(CodeTypeDeclaration codeType, CodeNamespace codeNamespace, bool promoteToNullable)
var toRemove = new List<CodeTypeMember>();
foreach (var property in codeType.Members.OfType<CodeMemberProperty>())
CodeMemberField backingField;
CodeMemberProperty specifiedProperty;
if (!property.TryGetBackingFieldAndSpecifiedProperty(codeType, out backingField, out specifiedProperty))
continue;
var specifiedField = specifiedProperty.GetBackingField(codeType);
if (specifiedField == null)
continue;
toRemove.Add(specifiedProperty);
toRemove.Add(specifiedField);
if (promoteToNullable)
// Do not do this for attributes
if (property.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.AttributeType.BaseType == typeof(System.Xml.Serialization.XmlAttributeAttribute).FullName))
continue;
var typeRef = property.Type;
if (typeRef.ArrayRank > 0)
// An array - not a reference type.
continue;
// OK, two possibilities here:
// 1) The property might reference some system type such as DateTime or decimal
// 2) The property might reference some type being defined such as an enum or struct.
var type = Type.GetType(typeRef.BaseType);
if (type != null)
if (!type.IsClass)
if (type == typeof(Nullable<>))
// Already nullable
continue;
else if (!type.IsGenericTypeDefinition && (type.IsValueType || type.IsEnum) && Nullable.GetUnderlyingType(type) == null)
var nullableType = typeof(Nullable<>).MakeGenericType(type);
var newRefType = new CodeTypeReference(nullableType);
property.Type = newRefType;
backingField.Type = newRefType;
else
var generatedType = codeNamespace.FindCodeType(typeRef);
if (generatedType != null)
if (generatedType.IsStruct || generatedType.IsEnum)
var newRefType = new CodeTypeReference(typeof(Nullable<>).FullName, typeRef);
property.Type = newRefType;
backingField.Type = newRefType;
foreach (var member in toRemove)
codeType.Members.Remove(member);
public static class CodeNamespaceExtensions
public static CodeTypeDeclaration FindCodeType(this CodeNamespace codeNamespace, CodeTypeReference reference)
if (codeNamespace == null)
throw new ArgumentNullException();
if (reference == null)
return null;
CodeTypeDeclaration foundType = null;
foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
if (codeType.Name == reference.BaseType)
if (foundType == null)
foundType = codeType;
else if (foundType != codeType)
foundType = null;
break;
return foundType;
public static class CodeMemberPropertyExtensions
public static bool TryGetBackingFieldAndSpecifiedProperty(this CodeMemberProperty property, CodeTypeDeclaration codeType,
out CodeMemberField backingField, out CodeMemberProperty specifiedProperty)
if (property == null)
backingField = null;
specifiedProperty = null;
return false;
if ((backingField = property.GetBackingField(codeType)) == null)
specifiedProperty = null;
return false;
specifiedProperty = null;
var specifiedName = property.Name + "Specified";
foreach (var p in codeType.Members.OfType<CodeMemberProperty>())
if (p.Name == specifiedName)
// Make sure the property is marked as XmlIgnore (there might be a legitimate, serializable property
// named xxxSpecified).
if (!p.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.AttributeType.BaseType == typeof(System.Xml.Serialization.XmlIgnoreAttribute).FullName))
continue;
if (specifiedProperty == null)
specifiedProperty = p;
else if (specifiedProperty != p)
specifiedProperty = null;
break;
if (specifiedProperty == null)
return false;
if (specifiedProperty.GetBackingField(codeType) == null)
return false;
return true;
public static CodeMemberField GetBackingField(this CodeMemberProperty property, CodeTypeDeclaration codeType)
if (property == null)
return null;
CodeMemberField returnedField = null;
foreach (var statement in property.GetStatements.OfType<CodeMethodReturnStatement>())
var expression = statement.Expression as CodeFieldReferenceExpression;
if (expression == null)
return null;
if (!(expression.TargetObject is CodeThisReferenceExpression))
return null;
var fieldName = expression.FieldName;
foreach (var field in codeType.Members.OfType<CodeMemberField>())
if (field.Name == fieldName)
if (returnedField == null)
returnedField = field;
else if (returnedField != field)
return null;
return returnedField;
public abstract class CustomXsdCodeGeneratorBase
// This base class adapted from http://mikehadlow.blogspot.com/2007/01/writing-your-own-xsdexe.html
readonly string Namespace;
public CustomXsdCodeGeneratorBase(string Namespace)
this.Namespace = Namespace;
public void XsdToClassTest(IEnumerable<string> xsds, TextWriter codeWriter)
XsdToClassTest(xsds.Select(xsd => (Func<TextReader>)(() => new StringReader(xsd))), codeWriter);
public void XsdToClassTest(IEnumerable<Func<TextReader>> xsds, TextWriter codeWriter)
var schemas = new XmlSchemas();
foreach (var getReader in xsds)
using (var reader = getReader())
var xsd = XmlSchema.Read(reader, null);
schemas.Add(xsd);
schemas.Compile(null, true);
var schemaImporter = new XmlSchemaImporter(schemas);
var maps = new List<XmlTypeMapping>();
foreach (XmlSchema xsd in schemas)
foreach (XmlSchemaType schemaType in xsd.SchemaTypes.Values)
maps.Add(schemaImporter.ImportSchemaType(schemaType.QualifiedName));
foreach (XmlSchemaElement schemaElement in xsd.Elements.Values)
maps.Add(schemaImporter.ImportTypeMapping(schemaElement.QualifiedName));
// create the codedom
var codeNamespace = new CodeNamespace(this.Namespace);
var codeExporter = new XmlCodeExporter(codeNamespace);
foreach (XmlTypeMapping map in maps)
codeExporter.ExportTypeMapping(map);
ModifyGeneratedNamespace(codeNamespace);
// Check for invalid characters in identifiers
CodeGenerator.ValidateIdentifiers(codeNamespace);
// output the C# code
var codeProvider = new CSharpCodeProvider();
codeProvider.GenerateCodeFromNamespace(codeNamespace, codeWriter, new CodeGeneratorOptions());
protected virtual void ModifyGeneratedNamespace(CodeNamespace codeNamespace)
foreach (CodeTypeDeclaration codeType in codeNamespace.Types)
ModifyGeneratedCodeTypeDeclaration(codeType, codeNamespace);
protected virtual void ModifyGeneratedCodeTypeDeclaration(CodeTypeDeclaration codeType, CodeNamespace codeNamespace)
为了测试它,我创建了以下类型:
namespace SampleClasses
public class SimleSampleClass
[XmlElement]
public decimal Something get; set;
[XmlIgnore]
public bool SomethingSpecified get; set;
[XmlRoot("RootClass")]
public class RootClass
[XmlArray]
[XmlArrayItem("SampleClass")]
public List<SampleClass> SampleClasses get; set;
[XmlRoot("SampleClass")]
public class SampleClass
[XmlAttribute]
public long Id get; set;
public decimal Something get; set;
[XmlIgnore]
public bool SomethingSpecified get; set;
public SomeEnum SomeEnum get; set;
[XmlIgnore]
public bool SomeEnumSpecified get; set;
public string SomeString get; set;
[XmlIgnore]
public bool SomeStringSpecified get; set;
public decimal? SomeNullable get; set;
[XmlIgnore]
public bool SomeNullableSpecified get; set;
public DateTime SomeDateTime get; set;
[XmlIgnore]
public bool SomeDateTimeSpecified get; set;
// https://***.com/questions/3280362/most-elegant-xml-serialization-of-color-structure
[XmlElement(Type = typeof(XmlColor))]
public Color MyColor get; set;
[XmlIgnore]
public bool MyColorSpecified get; set;
public enum SomeEnum
DefaultValue,
FirstValue,
SecondValue,
ThirdValue,
// https://***.com/questions/3280362/most-elegant-xml-serialization-of-color-structure
public struct XmlColor
private Color? color_;
private Color Color
get
return color_ ?? Color.Black;
set
color_ = value;
public XmlColor(Color c) color_ = c;
public Color ToColor()
return Color;
public void FromColor(Color c)
Color = c;
public static implicit operator Color(XmlColor x)
return x.ToColor();
public static implicit operator XmlColor(Color c)
return new XmlColor(c);
[XmlAttribute]
public string Web
get return ColorTranslator.ToHtml(Color);
set
try
if (Alpha == 0xFF) // preserve named color value if possible
Color = ColorTranslator.FromHtml(value);
else
Color = Color.FromArgb(Alpha, ColorTranslator.FromHtml(value));
catch (Exception)
Color = Color.Black;
[XmlAttribute]
public byte Alpha
get return Color.A;
set
if (value != Color.A) // avoid hammering named color if no alpha change
Color = Color.FromArgb(value, Color);
public bool ShouldSerializeAlpha() return Alpha < 0xFF;
使用通用 xsd.exe
我从它们生成了以下架构:
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="SimleSampleClass" nillable="true" type="SimleSampleClass" />
<xs:complexType name="SimleSampleClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" />
</xs:sequence>
</xs:complexType>
<xs:element name="RootClass" nillable="true" type="RootClass" />
<xs:complexType name="RootClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="SampleClasses" type="ArrayOfSampleClass" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ArrayOfSampleClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="SampleClass" nillable="true" type="SampleClass" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="SampleClass">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" />
<xs:element minOccurs="0" maxOccurs="1" name="SomeEnum" type="SomeEnum" />
<xs:element minOccurs="0" maxOccurs="1" name="SomeString" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="SomeNullable" nillable="true" type="xs:decimal" />
<xs:element minOccurs="0" maxOccurs="1" name="SomeDateTime" type="xs:dateTime" />
<xs:element minOccurs="0" maxOccurs="1" name="MyColor" type="XmlColor" />
</xs:sequence>
<xs:attribute name="Id" type="xs:long" use="required" />
</xs:complexType>
<xs:simpleType name="SomeEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="DefaultValue" />
<xs:enumeration value="FirstValue" />
<xs:enumeration value="SecondValue" />
<xs:enumeration value="ThirdValue" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="XmlColor">
<xs:attribute name="Web" type="xs:string" />
<xs:attribute name="Alpha" type="xs:unsignedByte" />
</xs:complexType>
<xs:element name="SampleClass" nillable="true" type="SampleClass" />
<xs:element name="SomeEnum" type="SomeEnum" />
<xs:element name="XmlColor" type="XmlColor" />
</xs:schema>
并且,使用此架构,我使用 CustomXsdCodeGenerator
和 promoteToNullable = true
和 Namespace = "Question42295155"
重新生成了以下 c# 类:
namespace Question42295155
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class SimleSampleClass
private System.Nullable<decimal> somethingField;
/// <remarks/>
public System.Nullable<decimal> Something
get
return this.somethingField;
set
this.somethingField = value;
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class SampleClass
private System.Nullable<decimal> somethingField;
private System.Nullable<SomeEnum> someEnumField;
private string someStringField;
private System.Nullable<decimal> someNullableField;
private System.Nullable<System.DateTime> someDateTimeField;
private XmlColor myColorField;
private long idField;
/// <remarks/>
public System.Nullable<decimal> Something
get
return this.somethingField;
set
this.somethingField = value;
/// <remarks/>
public System.Nullable<SomeEnum> SomeEnum
get
return this.someEnumField;
set
this.someEnumField = value;
/// <remarks/>
public string SomeString
get
return this.someStringField;
set
this.someStringField = value;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public System.Nullable<decimal> SomeNullable
get
return this.someNullableField;
set
this.someNullableField = value;
/// <remarks/>
public System.Nullable<System.DateTime> SomeDateTime
get
return this.someDateTimeField;
set
this.someDateTimeField = value;
/// <remarks/>
public XmlColor MyColor
get
return this.myColorField;
set
this.myColorField = value;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public long Id
get
return this.idField;
set
this.idField = value;
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public enum SomeEnum
/// <remarks/>
DefaultValue,
/// <remarks/>
FirstValue,
/// <remarks/>
SecondValue,
/// <remarks/>
ThirdValue,
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class XmlColor
private string webField;
private byte alphaField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Web
get
return this.webField;
set
this.webField = value;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte Alpha
get
return this.alphaField;
set
this.alphaField = value;
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class RootClass
private SampleClass[] sampleClassesField;
/// <remarks/>
public SampleClass[] SampleClasses
get
return this.sampleClassesField;
set
this.sampleClassesField = value;
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("XsdToClassTest", "1.0.0.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class ArrayOfSampleClass
private SampleClass[] sampleClassField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("SampleClass", IsNullable=true)]
public SampleClass[] SampleClass
get
return this.sampleClassField;
set
this.sampleClassField = value;
注意:
没有以Specified
结尾的属性。
属性Something
、SomeEnum
和SomeDateTime
已变为可为空。
已经可以为空的public decimal? SomeNullable get; set;
往返于public System.Nullable<decimal> SomeNullable
,而不是因为变成一些可怕的双空System.Nullable<System.Nullable<decimal>>
而失败。
然后我从最初的 RootClass
生成了以下 XML:
<RootClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SampleClasses>
<SampleClass Id="10101">
<Something>2.718</Something>
<SomeEnum>ThirdValue</SomeEnum>
<SomeString>hello</SomeString>
<SomeNullable>3.14</SomeNullable>
<SomeDateTime>2017-02-28T00:00:00-05:00</SomeDateTime>
<MyColor Web="Maroon" />
</SampleClass>
</SampleClasses>
</RootClass>
并且能够成功地将其反序列化为生成的类Question42295155.RootClass
,而不会丢失数据。
注意 - 此代码经过轻微测试。如果您愿意,我可以使用示例架构重新测试。
有关详细信息,请参阅Code Generation in the .NET Framework Using XML Schema。
【讨论】:
以上是关于XmlCodeExporter 和可为空的类型的主要内容,如果未能解决你的问题,请参考以下文章