如何确定哪个 class 成员导致 XmlSerializer 出现问题

How to identify which class member is causing issue with XmlSerializer

所以我在玩 XmlSerializer,遇到了一些挑战。 我有一个第三方 XML 文件,所以我受到 XML 模式的限制,我不是它的忠实粉丝..我想尝试从这个 XML 到 VBA,所以我需要通过 COM。

我已经设法让一些 classes 工作,主要是使用 XSD2 (CodeDom) 将 XML 模式转换为一组 classes,并操纵C# classes 使用 IList<> 实现 classes。我认为问题出在哪里。我已经用几个 classes 作为开始,它工作得很好,我已经通过 CodeDom 推动它,现在有 335 classes/enumerations 领域的东西要处理。

我得到的异常在线

XmlSerializer serializer = new XmlSerializer(typeof(L5XSchema.RSLogix5000Content));

例外情况是: 'CDF15337, DDB176069: Also fail in whidbey IEnumerable member with XmlAttributeAttribute' (link 是我能找到的异常的唯一在线参考 https://github.com/microsoft/referencesource/blob/master/System.Xml/System/Xml/Serialization/XmlSerializationWriterILGen.cs

有谁知道 'whidbey' 参考资料是关于什么的?

幸运的是,由于 IEnumerable XmlAttributeAttribute 部分,我相信我已经确定了问题。 确实如此。将它们转换回 Enum[] 允许构建 XmlSerializer 实例。我的 XML 此部分(属性之一)的架构如下所示:

<xs:attribute name="CIPAxisExceptionAction" use="optional">
  <xs:simpleType>
    <xs:list itemType="AxisExceptionActionEnum" />
  </xs:simpleType>
</xs:attribute>

有谁知道一种方法可以将 IList<> 之类的东西用于 XML 属性值?

.NET Framework 示例问题:https://dotnetfiddle.net/Ws5VMI
.NET Core 示例问题:https://dotnetfiddle.net/ShO5UX

我目前仍在努力根据架构获得 classes 'happy' 和 XmlSerializer。并且对 COM 也很满意。这意味着没有可识别的通用集合,所以我的所有 List<> 成员都需要包装在一个 IList<> 实现包装器 class 中才能通过 COM 障碍。在处理派生的 classes 时非常烦人。

有谁知道可以轻松地将 C# 中的泛型集合代理到 COM 的方法吗?

您发现 XmlSerializer 的 code-generation 算法中似乎存在错误,特别是 自定义 collection 标记为 [=19] 的枚举=] 未实现 non-generic 接口 System.Collections.ICollection。解决方法似乎是在自定义 collection.

上显式实现此接口

详情如下。假设我们有如下类型:

public class RSLogix5000Content
{
    [XmlAttribute] public List<AxisExceptionActionEnum>  CIPAxisExceptionAction { get; set; }
}

public enum AxisExceptionActionEnum
{
    Default = 0,
    Value1 = (1<<0),
    Value2 = (1<<1)
}

当序列化为XML时,属性被成功序列化为包含space-separated值序列的字符串,如下所示(demo fiddle #1 here):

<RSLogix5000Content CIPAxisExceptionAction="Value1 Value2 Value1" />

但是,如果 List<AxisExceptionActionEnum> 被自定义 collection 替换,实现 IList<AxisExceptionActionEnum> 就像这样(演示 fiddle #2 here):

public class RSLogix5000Content
{
    [XmlAttribute]
    public AxisExceptionActionEnumCollection CIPAxisExceptionAction { get; set; }
}

public enum AxisExceptionActionEnum
{
    Default = 0,
    Value1 = (1<<0),
    Value2 = (1<<1)
}

public class AxisExceptionActionEnumCollection : IList<AxisExceptionActionEnum>
{
    private System.Collections.Generic.List<AxisExceptionActionEnum> axisExceptionActionEnumField = new System.Collections.Generic.List<AxisExceptionActionEnum>();

    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public AxisExceptionActionEnum this[int index] {
        get {
            return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField))[index];
        }
        set {
            ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField))[index] = value;
        }
    }

    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public int Count {
        get {
            return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).Count;
        }
    }

    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public bool IsReadOnly {
        get {
            return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).IsReadOnly;
        }
    }

    public void Add(AxisExceptionActionEnum item) {
        ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).Add(item);
    }

    public void Clear() {
        ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).Clear();
    }

    public bool Contains(AxisExceptionActionEnum item) {
        return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).Contains(item);
    }

    public void CopyTo(AxisExceptionActionEnum[] array, int arrayIndex) {
        ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).CopyTo(array, arrayIndex);
    }

    public int IndexOf(AxisExceptionActionEnum item) {
        return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).IndexOf(item);
    }

    public void Insert(int index, AxisExceptionActionEnum item) {
        ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).Insert(index, item);
    }

    public bool Remove(AxisExceptionActionEnum item) {
        return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).Remove(item);
    }

    public void RemoveAt(int index) {
        ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).RemoveAt(index);
    }

    public System.Collections.Generic.IEnumerator<AxisExceptionActionEnum> GetEnumerator() {
        return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return ((System.Collections.Generic.IList<AxisExceptionActionEnum>)(this.axisExceptionActionEnumField)).GetEnumerator();
    }
}

然后抛出一个莫名其妙的异常:

System.NotSupportedException: Also fail in IEnumerable member with XmlAttributeAttribute
   at System.Xml.Serialization.XmlSerializationWriterILGen.WriteMember(SourceInfo source, AttributeAccessor attribute, TypeDesc memberTypeDesc, String parent)
   at System.Xml.Serialization.XmlSerializationWriterILGen.WriteStructMethod(StructMapping mapping)
   at System.Xml.Serialization.XmlSerializationWriterILGen.GenerateMethod(TypeMapping mapping)
   at System.Xml.Serialization.XmlSerializationILGen.GenerateReferencedMethods()
   at System.Xml.Serialization.XmlSerializationWriterILGen.GenerateEnd()
   at System.Xml.Serialization.TempAssembly.GenerateRefEmitAssembly(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace)
   at System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, String location)
   at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlMapping xmlMapping, Type type, String defaultNamespace, String location)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type)

为什么会发生这种情况?好吧,List<AxisExceptionActionEnum>AxisExceptionActionEnumCollection 之间的一个区别是前者实现了旧的 non-generic 接口 IListICollection,也许缺少 non-generic访问 collection 是否导致序列化程序出现问题?为了测试这个,我为你的 collection:

实现了 ICollection
public class AxisExceptionActionEnumCollection : IList<AxisExceptionActionEnum>, ICollection
{
    // Remainder unchanged

    #region ICollection Members

    void ICollection.CopyTo(Array array, int index)
    {
        ((System.Collections.IList)(this.axisExceptionActionEnumField)).CopyTo(array, index);
    }

    int ICollection.Count { get { return Count; } }

    bool ICollection.IsSynchronized { get { return ((System.Collections.IList)(this.axisExceptionActionEnumField)).IsSynchronized; } }

    object ICollection.SyncRoot { get { return ((System.Collections.IList)(this.axisExceptionActionEnumField)).SyncRoot; } }

    #endregion
}

问题自行解决!演示 fiddle #3 here.

备注:

  • 只需要实现 non-generic ICollection 接口 -- 而不是 non-generic IList.

  • XmlSerializer 非常乐意序列化您的 generic-only collection 只要它标有 [XmlElement] 而不是[XmlAttribute]:

    public class RSLogix5000Content
    {
        [XmlElement]
        public AxisExceptionActionEnumCollection CIPAxisExceptionAction { get; set; }
    }
    

    演示 fiddle #4 here.

  • 因为 ICollection 甚至没有 Add(object value) 方法,这绝对感觉像是一个 XmlSerializer 错误。您可能想向 Microsoft 报告问题,例如here.

  • 将 collection 个枚举序列化为属性会导致 space-separated 个枚举值序列,这让我感到有些惊讶; docs 中没有任何内容表明这应该有效。

    但是由于 [Flags] 枚举也被序列化为 space-separated 枚举值序列,似乎尝试将标志枚举列表序列化为 XML 属性也失败了有一个难以理解的例外:

    public class RSLogix5000Content
    {
        [XmlAttribute] public List<AxisExceptionActionEnum>  CIPAxisExceptionAction { get; set; }
    }
    
    [Flags]
    public enum AxisExceptionActionEnum
    {
        Default = 0,
        Value1 = (1<<0),
        Value2 = (1<<1)
    }
    
    Failed with unhandled exception: 
    System.InvalidOperationException: There was an error reflecting type 'RSLogix5000Content'.
    ---> System.InvalidOperationException: There was an error reflecting property 'CIPAxisExceptionAction'.
    ---> System.InvalidOperationException: There was an error reflecting type 'AxisExceptionActionEnum'.
    ---> System.FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
    at System.Text.StringBuilder.AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
    at System.String.FormatHelper(IFormatProvider provider, String format, ParamsArray args)
    at System.String.Format(String format, Object arg0)
    at System.SR.Format(String resourceFormat, Object p1)
    at System.Xml.Serialization.XmlReflectionImporter.ImportEnumMapping(EnumModel model, String ns, Boolean repeats)
    

    演示 fiddle #5 here.