XML ошибка десериализации в Xamarin Android в списке <int>помечена [XmlAttribute] - PullRequest
1 голос
/ 13 июля 2020

У меня проблема с десериализацией объекта, который используется в многоплатформенном проекте как на Windows, так и на Xamarin Android.

Класс ConfigProperty создается, используется и заполняется программой а затем сериализован в XML для хранения. После чтения и десериализации класса в Android я получаю System.ArgumentNullException: Value cannot be null. Parameter name: elementType. Десериализация в Windows работает безупречно.

Я сузил проблему до свойства DeviceIdentifierIndices, списка целых чисел. Когда атрибута нет, т.е. список был пуст при сериализации, десериализация также работает. Что действительно странно, так это то, что он также работает на обеих платформах, когда список сериализуется как обычный элемент XML, а не как атрибут XML.

Прямо сейчас я нахожусь на этапе, когда я могу изменить от атрибута к элементу, но я бы предпочел сохранить его как атрибут.

Рассматриваемый класс выглядит так, но проблема существует и в других классах:

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 де- / сериализация выполняется обычным 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 }

1 Ответ

0 голосов
/ 18 июля 2020

Ваш существующий класс использует следующие неясно задокументированные функции XmlSerializer, а именно то, что [XmlAttribute] можно применить к коллекции примитивов, и эта коллекция будет сериализована как XML атрибут, значение которого представляет собой последовательность примитивных значений, разделенных пробелами.

В частности, Примечания для XmlAttributeAttribute состояния:

Вы можете назначить атрибут XmlAttributeAttribute только для полей publi c или свойств publi c, которые возвращают значение (или массив значений) , которое может быть сопоставлено с одним из простых типов XML языка определения схемы (XSD) (включая все встроенные типы данных, производные от типа XSD anySimpleType). Возможные типы включают любой, который может быть сопоставлен с простыми типами XSD, включая Guid, Char и перечисления.

Но как именно «массив значений» сериализуется в виде строки? Если я упрощу вашу модель следующим образом:

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, используя xsd.exe, я получу:

<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> - это задокументировано Microsoft от до Определите коллекцию из одного simpleType определения и W3 C XML языка определения схемы (XSD) 1.1 Часть 2: Типы данных 2.4.1.2 Типы данных списка состояния

Лексическое пространство · списка · Тип данных - это набор · литералов · каждый из которых представляет собой разделенную пробелами последовательность · литералов · элемента введите ·.

И именно поэтому DeviceIdentifierIndices успешно сериализуется как "18 22" на. Net.

Все, как говорится, это очень непонятная функциональность. Вполне возможно, что команда Mono никогда не реализовывала и не тестировала эту функцию должным образом. (Этот похожий по звучанию сообщение на форуме Unity предполагает, что другие видят ту же проблему.) Таким образом, вам может потребоваться изменить свой класс, чтобы обойти ограничение.

У меня нет 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>();
    }
}

И на самом деле источник Mono для XmlSerializationReaderInterpreter.ReadListString(), похоже, предполагает что listType является типом массива (а не общим типом c, например List<int>), так что это может сработать для вас. ( Обновление : OP пишет в комментариях : Я успешно попробовал ваше первое решение, так как оно кажется мне самым чистым , поэтому это рекомендуемое решение.)

В противном случае создание свойства суррогатной строки и выполнение синтаксического анализа вручную должно работать:

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>();
    }
}

Обратите внимание, однако, что XSD, сгенерированный для второго класса обходного пути, не будет идентичен XSD из исходного class.

Сериализация DeviceIdentifierIndices как элемента, а не атрибута также будет работать, потому что коллекции простых типов не сериализуются как типы данных списка при сериализации как элемент, вместо этого они сериализуются как последовательности элементов, возможно с элемент контейнера, в котором используется совершенно другой путь кода.

Демо-скрипт, обменивающийся XML между тремя классами: https://dotnetfiddle.net/3PmZHv

...