xsi:type 属性搞乱了 C# XML 反序列化

xsi:type attribute messing up C# XML deserialization

我使用 XSD.exe 基于 XML 模式(.xsd 文件)自动生成 C# 对象。我正在反序列化 OpenCover 输出,但部分 classes 之一没有正确生成。

这是导致异常的行:

<MethodPoint xsi:type="SequencePoint" vc="0" uspid="1" ordinal="0" offset="0" sl="19" sc="9" el="19" ec="10" bec="0" bev="0" fileid="1" />

这是 MethodPoint 的简化版本 class:

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class CoverageSessionModulesModuleClassesClassMethodsMethodMethodPoint {
    private string vcField;
    private string uspidField;
    private string ordinalField;
    private string offsetField;
    private string slField;
    private string scField;
    private string elField;
    private string ecField;
    private string becField;
    private string bevField;
    private string fileidField;
}

现在我浏览了很多 .xml 文件,但 OpenCover 输出文件是唯一在属性中包含冒号的文件。 MethodPoint 对象也是唯一在属性中包含冒号的对象。如您所见,class 不包含 xsi:type 属性,而且我知道仅仅添加它是行不通的,因为有冒号。你如何处理 xsi 前缀?

这是从 OpenCover XML 文件之一生成的原始 .xsd

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="CoverageSession" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="Summary">
    <xs:complexType>
      <xs:attribute name="numSequencePoints" type="xs:string" />
      <xs:attribute name="visitedSequencePoints" type="xs:string" />
      <xs:attribute name="numBranchPoints" type="xs:string" />
      <xs:attribute name="visitedBranchPoints" type="xs:string" />
      <xs:attribute name="sequenceCoverage" type="xs:string" />
      <xs:attribute name="branchCoverage" type="xs:string" />
      <xs:attribute name="maxCyclomaticComplexity" type="xs:string" />
      <xs:attribute name="minCyclomaticComplexity" type="xs:string" />
      <xs:attribute name="visitedClasses" type="xs:string" />
      <xs:attribute name="numClasses" type="xs:string" />
      <xs:attribute name="visitedMethods" type="xs:string" />
      <xs:attribute name="numMethods" type="xs:string" />
    </xs:complexType>
  </xs:element>
  <xs:element name="CoverageSession" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="Summary" />
        <xs:element name="Modules">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Module" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="FullName" type="xs:string" minOccurs="0" msdata:Ordinal="1" />
                    <xs:element name="ModuleName" type="xs:string" minOccurs="0" msdata:Ordinal="2" />
                    <xs:element ref="Summary" minOccurs="0" maxOccurs="unbounded" />
                    <xs:element name="Files" minOccurs="0" maxOccurs="unbounded">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element name="File" minOccurs="0" maxOccurs="unbounded">
                            <xs:complexType>
                              <xs:attribute name="uid" type="xs:string" />
                              <xs:attribute name="fullPath" type="xs:string" />
                            </xs:complexType>
                          </xs:element>
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                    <xs:element name="Classes" minOccurs="0" maxOccurs="unbounded">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element name="Class" minOccurs="0" maxOccurs="unbounded">
                            <xs:complexType>
                              <xs:sequence>
                                <xs:element name="FullName" type="xs:string" minOccurs="0" />
                                <xs:element ref="Summary" minOccurs="0" maxOccurs="unbounded" />
                                <xs:element name="Methods" minOccurs="0" maxOccurs="unbounded">
                                  <xs:complexType>
                                    <xs:sequence>
                                      <xs:element name="Method" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                          <xs:sequence>
                                            <xs:element name="MetadataToken" type="xs:string" minOccurs="0" msdata:Ordinal="1" />
                                            <xs:element name="Name" type="xs:string" minOccurs="0" msdata:Ordinal="2" />
                                            <xs:element ref="Summary" minOccurs="0" maxOccurs="unbounded" />
                                            <xs:element name="FileRef" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:attribute name="uid" type="xs:string" />
                                              </xs:complexType>
                                            </xs:element>
                                            <xs:element name="SequencePoints" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:sequence>
                                                  <xs:element name="SequencePoint" minOccurs="0" maxOccurs="unbounded">
                                                    <xs:complexType>
                                                      <xs:attribute name="vc" type="xs:string" />
                                                      <xs:attribute name="uspid" type="xs:string" />
                                                      <xs:attribute name="ordinal" type="xs:string" />
                                                      <xs:attribute name="offset" type="xs:string" />
                                                      <xs:attribute name="sl" type="xs:string" />
                                                      <xs:attribute name="sc" type="xs:string" />
                                                      <xs:attribute name="el" type="xs:string" />
                                                      <xs:attribute name="ec" type="xs:string" />
                                                      <xs:attribute name="bec" type="xs:string" />
                                                      <xs:attribute name="bev" type="xs:string" />
                                                      <xs:attribute name="fileid" type="xs:string" />
                                                    </xs:complexType>
                                                  </xs:element>
                                                </xs:sequence>
                                              </xs:complexType>
                                            </xs:element>
                                            <xs:element name="BranchPoints" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:sequence>
                                                  <xs:element name="BranchPoint" minOccurs="0" maxOccurs="unbounded">
                                                    <xs:complexType>
                                                      <xs:attribute name="vc" type="xs:string" />
                                                      <xs:attribute name="uspid" type="xs:string" />
                                                      <xs:attribute name="ordinal" type="xs:string" />
                                                      <xs:attribute name="offset" type="xs:string" />
                                                      <xs:attribute name="sl" type="xs:string" />
                                                      <xs:attribute name="path" type="xs:string" />
                                                      <xs:attribute name="offsetend" type="xs:string" />
                                                      <xs:attribute name="fileid" type="xs:string" />
                                                      <xs:attribute name="offsetchain" type="xs:string" />
                                                    </xs:complexType>
                                                  </xs:element>
                                                </xs:sequence>
                                              </xs:complexType>
                                            </xs:element>
                                            <xs:element name="MethodPoint" minOccurs="0" maxOccurs="unbounded">
                                              <xs:complexType>
                                                <xs:attribute name="vc" type="xs:string" />
                                                <xs:attribute name="uspid" type="xs:string" />
                                                <xs:attribute name="ordinal" type="xs:string" />
                                                <xs:attribute name="offset" type="xs:string" />
                                                <xs:attribute name="sl" type="xs:string" />
                                                <xs:attribute name="sc" type="xs:string" />
                                                <xs:attribute name="el" type="xs:string" />
                                                <xs:attribute name="ec" type="xs:string" />
                                                <xs:attribute name="bec" type="xs:string" />
                                                <xs:attribute name="bev" type="xs:string" />
                                                <xs:attribute name="fileid" type="xs:string" />
                                              </xs:complexType>
                                            </xs:element>
                                          </xs:sequence>
                                          <xs:attribute name="visited" type="xs:string" />
                                          <xs:attribute name="cyclomaticComplexity" type="xs:string" />
                                          <xs:attribute name="sequenceCoverage" type="xs:string" />
                                          <xs:attribute name="branchCoverage" type="xs:string" />
                                          <xs:attribute name="isConstructor" type="xs:string" />
                                          <xs:attribute name="isStatic" type="xs:string" />
                                          <xs:attribute name="isGetter" type="xs:string" />
                                          <xs:attribute name="isSetter" type="xs:string" />
                                          <xs:attribute name="skippedDueTo" type="xs:string" />
                                        </xs:complexType>
                                      </xs:element>
                                    </xs:sequence>
                                  </xs:complexType>
                                </xs:element>
                              </xs:sequence>
                            </xs:complexType>
                          </xs:element>
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                  </xs:sequence>
                  <xs:attribute name="skippedDueTo" type="xs:string" />
                  <xs:attribute name="hash" type="xs:string" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

简短的回答 是您需要手动将 [XmlInclude(typeof(SequencePoint))] 添加到您的 MethodPoint class:

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[XmlInclude(typeof(SequencePoint))]
public partial class CoverageSessionModulesModuleClassesClassMethodsMethodMethodPoint {
    private string vcField;
    private string uspidField;
    private string ordinalField;
    private string offsetField;
    private string slField;
    private string scField;
    private string elField;
    private string ecField;
    private string becField;
    private string bevField;
    private string fileidField;
}

您还需要让 SequencePointMethodPoint 继承,如果它还没有这样做的话。

您需要这样做是因为,当您使用 xsd.exe 从 XML 样本 生成 XSD,然后使用 c# class 反过来,当属性 xsi:type="SomePolymoirphicSubType" 出现在 XML 中时,它显然不会自动将多态子类型属性添加到基类型,即使它看起来应该如此。

解释如下。 xsi:type 属性是 {http://www.w3.org/2001/XMLSchema-instance}type 的缩写,是 w3c standard attribute that allows an element to explicitly assert its type, e.g. when it is a polymorphic subtype of the expected element type. XmlSerializer supports this attribute and will use it to determine the actual type of object to deserialize for such a polymorphic type. However, it requires to be informed in advance of all possible types using XmlIncludeAttribute。因此,如果我创建以下类型层次结构:

[XmlInclude(typeof(SequencePoint))]
public class MethodPoint
{
}

public class SequencePoint : MethodPoint
{
}

并序列化如下:

var test = new SequencePoint();

var serializer = new XmlSerializer(typeof(MethodPoint));
var sb = new StringBuilder();
using (var stream = new StringWriter(sb))
    serializer.Serialize(stream, test);

Console.WriteLine(sb);

我得到以下 XML:

<MethodPoint 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
 xsi:type="SequencePoint" />

然后如果我使用 var serializer = new XmlSerializer(typeof(MethodPoint)) 反序列化它,我会得到一个 SequencePoint,而不是它的基础 class。如果我使用 xsd.exe 为这些 class 生成模式,我会得到:

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="MethodPoint" nillable="true" type="MethodPoint" />
  <xs:complexType name="MethodPoint" />
  <xs:complexType name="SequencePoint">
    <xs:complexContent mixed="false">
      <xs:extension base="MethodPoint" />
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="SequencePoint" nillable="true" type="SequencePoint" />
</xs:schema>

注意到 xs:extension 了吗?这就是 XSD 表示多态子类型的方式。然后如果我 运行 xsd.exe 向后重新生成我的 classes,我得到:

[System.Xml.Serialization.XmlIncludeAttribute(typeof(SequencePoint))]
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class MethodPoint {
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)]
public partial class SequencePoint : MethodPoint {
}

如您所见,XmlIncludeAttribute 就在那里,生成的 classes 等同于原始文件。到目前为止一切正常。

但是,似乎在从示例 XML 文件推断 XSD 时,xsd.exe 没有发现 xsi:type 属性的存在。例如,如果我从上面的普通 XML 创建一个 XSD,结果是:

<xs:schema id="MethodPoint" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="MethodPoint" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded" />
    </xs:complexType>
  </xs:element>
</xs:schema>

多态子类型完全缺失。从此 XSD 生成的 类 将无法反序列化 XML.

因此,从带有 xsd.exe 的 XML 样本生成 c# classes 似乎不如从适当的 XSD 生成它们可靠。具体来说,在 XML 文件中出现 xsi:type 的情况下,您将需要手动修复生成的 classes 或生成的 XSD 以实现所需的层次结构。这可能是工具中的限制或错误。

(limitation/bug也会出现在内部使用xsd.exePaste XML as Classes中。)