XML 标有 [XmlAttribute] 的列表 <int> 上的 Xamarin Android 中的反序列化错误

XML deserialization error in Xamarin Android on List<int> marked with [XmlAttribute]

我在反序列化在 Windows 和 Xamarin Android.

的多平台项目中使用的对象时遇到问题

ConfigPropertyclass由程序创建、使用和填充,然后序列化为XML存储。在读取并反序列化 Android 中的 class 后,我得到一个 System.ArgumentNullException: Value cannot be null. Parameter name: elementType。 Windows 中的反序列化工作完美。

我将问题缩小到 DeviceIdentifierIndices 属性,一个整数列表。当属性不存在时,即列表在序列化时为空,反序列化也有效。真正奇怪的是,当列表被序列化为通常的 XML 元素而不是 XML 属性时,它也适用于两个平台。

现在我正处于可以从属性更改为元素的阶段,但我更愿意将其保留为属性。

有问题的 class 看起来像这样,但其他 classes 也存在问题:

public class ConfigProperty
{
    public ushort E2Address { get; set; }
    public string NameKey { get; set; }
    public string DescriptionKey { get; set; }
    public Visibility Visibility { get; set; }                // 0: Invisible 1: R 2: RW 3: W
    [XmlAttribute]
    public List<int> DeviceIdentifierIndices { get; private set; }
    ...

    public ConfigProperty() 
    {
        NameKey = string.Empty;
        DescriptionKey = string.Empty;
        Visibility = Visibility.ReadWrite;
        DeviceIdentifierIndices = new List<int>();
        ...
    }
}

生成的 XML 如下所示:

<ConfigProperty DeviceIdentifierIndices="18 22">
    <E2Address>327</E2Address>
    <NameKey>lightningInterval</NameKey>
    <DescriptionKey>lightningIntervalDescription</DescriptionKey>
    <Visibility>2</Visibility>
    ...
</ConfigProperty>

XML de-/serialization用普通的XmlSerializers:

public static void Serialize<T>(T data, Stream stream)
{
    XmlWriterSettings settings = new XmlWriterSettings
    {
        CloseOutput = false,
        Encoding = Encoding.UTF8,
        Indent = true
    };

    using(XmlWriter tw = XmlWriter.Create(stream, settings))
    {
        XmlSerializer xs = new XmlSerializer(typeof(T));
        xs.Serialize(tw, data);
    }
}

public static T Deserialize<T>(Stream stream)
{
    using(XmlReader reader = XmlReader.Create(stream))
    {
        XmlSerializer xs = new XmlSerializer(typeof(T));
        return (T)xs.Deserialize(reader);
    }
}

...

ConfigProperty config = Export.XmlExport.Deserialize<ConfigProperty>(inputStream);

这是失败的反序列化尝试的堆栈跟踪:

{System.ArgumentNullException: Value cannot be null.
Parameter name: elementType
  at System.Array.CreateInstance (System.Type elementType, System.Int32[] lengths) [0x00009] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/corlib/System/Array.cs:471 
  at System.Array.CreateInstance (System.Type elementType, System.Int32 length) [0x0000b] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/corlib/System/Array.cs:451 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadListString (System.Xml.Serialization.XmlTypeMapping typeMap, System.String values) [0x00044] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:725 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.GetValueFromXmlString (System.String value, System.Xml.Serialization.TypeData typeData, System.Xml.Serialization.XmlTypeMapping typeMap) [0x00009] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:658 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadAttributeMembers (System.Xml.Serialization.ClassMap map, System.Object ob, System.Boolean isValueList) [0x00030] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:255 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadMembers (System.Xml.Serialization.ClassMap map, System.Object ob, System.Boolean isValueList, System.Boolean readBySoapOrder) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:295 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstanceMembers (System.Xml.Serialization.XmlTypeMapping typeMap, System.Object ob) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:240 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstance (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Boolean checkType) [0x000c4] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:230 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObject (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Boolean checkType) [0x0002e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:193 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObjectElement (System.Xml.Serialization.XmlTypeMapElementInfo elem) [0x00059] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:632 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadListElement (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Object list, System.Boolean canCreateInstance) [0x000d7] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:696 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadMembers (System.Xml.Serialization.ClassMap map, System.Object ob, System.Boolean isValueList, System.Boolean readBySoapOrder) [0x00490] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:423 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstanceMembers (System.Xml.Serialization.XmlTypeMapping typeMap, System.Object ob) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:240 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstance (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Boolean checkType) [0x000c4] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:230 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObject (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Boolean checkType) [0x0002e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:193 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObjectElement (System.Xml.Serialization.XmlTypeMapElementInfo elem) [0x00059] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:632 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadListElement (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Object list, System.Boolean canCreateInstance) [0x000d7] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:696 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadMembers (System.Xml.Serialization.ClassMap map, System.Object ob, System.Boolean isValueList, System.Boolean readBySoapOrder) [0x00549] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:435 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstanceMembers (System.Xml.Serialization.XmlTypeMapping typeMap, System.Object ob) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:240 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstance (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Boolean checkType) [0x000c4] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:230 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObject (System.Xml.Serialization.XmlTypeMapping typeMap, System.Boolean isNullable, System.Boolean checkType) [0x0002e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:193 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadRoot (System.Xml.Serialization.XmlTypeMapping rootMap) [0x00056] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:184 
  at System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadRoot () [0x00022] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializationReaderInterpreter.cs:87 
  at System.Xml.Serialization.XmlSerializer.Deserialize (System.Xml.Serialization.XmlSerializationReader reader) [0x0005e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializer.cs:381 
  at System.Xml.Serialization.XmlSerializer.Deserialize (System.Xml.XmlReader xmlReader) [0x00026] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/System.XML/System.Xml.Serialization/XmlSerializer.cs:358 
  at Export.XmlExport.Deserialize[T] (System.IO.Stream stream) [0x0002d] in C:\Projekte\xxx\Code\Export\XmlExport.cs:91 
  at Config.DeviceConfigInfo.FromStream (System.IO.Stream inputStream, Config.DeviceConfigInfoType type) [0x00118] in C:\Projekte\xxx\Code\Config\DeviceConfig.cs:872 
  at Config.DeviceConfigInfo.FromUri (Android.Content.Context context, Android.Net.Uri uri, System.Boolean decrypt) [0x0001d] in C:\Projekte\xxx\Code\Config\DeviceConfig.cs:816 
  at xxx.Activities.WorkspaceActivity.OnActivityResult (System.Int32 requestCode, Android.App.Result resultCode, Android.Content.Intent data) [0x00085] in C:\Projekte\xxx\Activities\WorkspaceActivity.cs:249 }

您现有的 class 正在利用 XmlSerializer 的以下模糊记录的功能,即 [XmlAttribute] 可以应用于基元集合,并且集合将被序列化作为一个 XML 属性,其值为 space 分隔的原始值序列。

具体来说,Remarks for XmlAttributeAttribute 状态:

You can assign the XmlAttributeAttribute only to public fields or public properties that return a value (or array of values) that can be mapped to one of the XML Schema definition language (XSD) simple types (including all built-in datatypes derived from the XSD anySimpleType type). The possible types include any that can be mapped to the XSD simple types, including Guid, Char, and enumerations.

但是“值数组”究竟是如何序列化为字符串的呢?如果我按如下方式简化您的模型:

public class ConfigProperty
{
    public string NameKey { get; set; }
    // Remainder of properties omitted for brevity
    [XmlAttribute]
    public List<int> DeviceIdentifierIndices { get; private set; }

    public ConfigProperty()
    {
        NameKey = string.Empty;
        DeviceIdentifierIndices = new List<int>();
    }
}

并使用 xsd.exe 为其生成 XSD,我得到:

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="ConfigProperty" nillable="true" type="ConfigProperty" />
  <xs:complexType name="ConfigProperty">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="1" name="NameKey" type="xs:string" />
    </xs:sequence>
    <xs:attribute name="DeviceIdentifierIndices" use="required">
      <xs:simpleType>
        <xs:list itemType="xs:int" />
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
</xs:schema>

其中 <xs:list>documented by Microsoft to Define a collection of a single simpleType definition and W3C XML Schema Definition Language (XSD) 1.1 Part 2: Datatypes 2.4.1.2 List Datatypes 状态

The ·lexical space· of a ·list· datatype is a set of ·literals· each of which is a space-separated sequence of ·literals· of the ·item type·.

这就是 DeviceIdentifierIndices 在 .Net 上成功序列化为 "18 22" 的原因。

综上所述,这是一个非常晦涩的功能。 Mono 团队很可能从未正确实现或测试过此功能。 (这 similar-sounding Unity forum post 表明其他人也遇到了同样的问题。)因此您可能需要修改 class 以解决该限制。

我没有可用于测试的 Xamarin Android,但用数组替换列表可能会解决问题:

public class ConfigProperty
{
    public string NameKey { get; set; }
    // Remainder of properties omitted for brevity

    [XmlAttribute("DeviceIdentifierIndices")]
    public int[] DeviceIdentifierIndicesArray
    {
        get { return DeviceIdentifierIndices.ToArray(); }
        set
        {
            DeviceIdentifierIndices.Clear();
            if (value != null)
                DeviceIdentifierIndices.AddRange(value);
        }
    }

    [XmlIgnore]
    public List<int> DeviceIdentifierIndices { get; private set; }

    public ConfigProperty()
    {
        NameKey = string.Empty;
        DeviceIdentifierIndices = new List<int>();
    }
}

事实上,XmlSerializationReaderInterpreter.ReadListString() seems to assume that listType is an array type (rather than a generic type like List<int>) so this might well work for you. (Update: OP writes 的 Mono 源代码:我已经成功地尝试了您的第一个解决方案,因为这对我来说似乎是最干净的,所以这是推荐的解决方案。 )

如果没有,创建代理项字符串 属性 并手动进行解析应该可行:

public class ConfigProperty
{
    public string NameKey { get; set; }
    // Remainder of properties omitted for brevity

    [XmlAttribute("DeviceIdentifierIndices")]
    public string XmlDeviceIdentifierIndices
    {
        get { return String.Join(" ", DeviceIdentifierIndices.Select(i => XmlConvert.ToString(i))); }
        set
        {
            var newList = (value ?? "").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(s => XmlConvert.ToInt32(s)).ToList();
            DeviceIdentifierIndices.Clear();
            DeviceIdentifierIndices.AddRange(newList);
        }
    }

    [XmlIgnore]
    public List<int> DeviceIdentifierIndices { get; private set; }

    public ConfigProperty()
    {
        NameKey = string.Empty;
        DeviceIdentifierIndices = new List<int>();
    }
}

但是请注意,为第二个解决方法 class 生成的 XSD 将与原始 class 中的 XSD 不同。

DeviceIdentifierIndices 序列化为元素而不是属性也会起作用,因为简单类型的集合在序列化为元素时不会序列化为列表数据类型,而是序列化为元素序列,可能带有容器元素,它使用完全不同的代码路径。

演示 fiddle 在三个 class 之间交换 XML 此处:https://dotnetfiddle.net/3PmZHv