C# Xml Serializer десериализует список для подсчета 0 вместо нуля

Я не понимаю, как работает XmlSerializer. сцены. У меня есть класс, который десериализует XML в объект. То, что я вижу, относится к следующим двум элементам, которые НЕ являются частью десериализуемого Xml.

[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
    private string comments;
    public string Comments
    {
        set { comments = value; }
        get { return comments; }
    }

    private System.Collections.Generic.List<string> tests = null;
    public System.Collections.Generic.List<string> Tests
    {
        get { return tests; }
        set { tests = value; }
    }
}

В качестве примера возьмем следующий XML:

<MyClass>
  <SomeNode>value</SomeNode>
</MyClass>

Вы заметили, что тесты и комментарии НЕ являются частью XML.

Когда этот XML десериализуется, комментарии будут нулевыми (что ожидается), а тесты будут пустым списком со счетчиком 0.

Если бы кто-то мог объяснить это мне, это было бы очень признательно. Что бы я предпочел, так это то, что если <Tests> отсутствует в XML, то список должен оставаться нулевым, но если присутствует (возможно, пустой) узел <Tests />, тогда список должен быть выделен.


person Maxqueue    schedule 27.07.2017    source источник
comment
@gdir вы говорите, что когда вы сериализуете это, вы получаете нулевое значение для списка вместо пустого списка?   -  person Maxqueue    schedule 27.07.2017
comment
Нет, я спрашиваю, что, поскольку тесты НЕ являются частью xml, при сериализации он должен быть нулевым, а не пустым списком. Имеет ли это смысл?   -  person Maxqueue    schedule 27.07.2017
comment
Вопрос OP касается десериализации, а не сериализации. Когда приведенный выше XML десериализуется, выделяется коллекция tests, хотя <Tests /> никогда не появляется в XML. (Кстати, я могу воспроизвести его.)   -  person dbc    schedule 27.07.2017
comment
Отредактировано для десериализации спасибо   -  person Maxqueue    schedule 27.07.2017
comment
Я не знаю, где это поведение задокументировано. Когда <Tests> полностью отсутствует, вы не хотите выделять список. Что вы хотите, когда есть пустой узел <Tests />?   -  person dbc    schedule 27.07.2017
comment
Для меня имеет смысл, чтобы ‹Tests/› был пустым списком. Но если тестов нет, то он должен быть нулевым.   -  person Maxqueue    schedule 27.07.2017
comment
Оказывается, он ведет себя так, как ожидалось, при использовании массива вместо списка.   -  person Maxqueue    schedule 27.07.2017


Ответы (4)


Вы наблюдаете, что члены, ссылающиеся на изменяемые коллекции, такие как List<T>, автоматически предварительно выделяются XmlSerializer в начале десериализации. Я не знаю ни одного места, где такое поведение задокументировано. Это может быть связано с поведением, описанным в этом ответе на XML Десериализация свойства коллекции с кодом по умолчанию, что объясняет, поскольку XmlSerializer поддерживает добавление для предварительно выделенных и доступных только для чтения коллекций, если предварительно выделенная коллекция содержит элементы по умолчанию, к ней будут добавлены десериализованные элементы, что может привести к дублированию содержимого. Microsoft, возможно, просто выбрала предварительное выделение всех изменяемых коллекций в начале десериализации как самый простой способ реализации этого.

Обходной путь из этого ответа, а именно использование свойства суррогатного массива, также работает здесь. Так как массив не может быть добавлен, XmlSerializer должен собрать все значения и установить их обратно после завершения десериализации. Но если соответствующий тег никогда не встречается, XmlSerializer, по-видимому, не начинает накапливать значения и поэтому не вызывает установщик массива. Кажется, это предотвращает предварительное выделение коллекций по умолчанию, которые вам не нужны:

[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
    private string comments;
    public string Comments
    {
        set { comments = value; }
        get { return comments; }
    }

    private System.Collections.Generic.List<string> tests = null;

    [XmlIgnore]
    public System.Collections.Generic.List<string> Tests
    {
        get { return tests; }
        set { tests = value; }
    }

    [XmlArray("Tests")]
    public string[] TestsArray
    {
        get
        {
            return (Tests == null ? null : Tests.ToArray());
        }
        set
        {
            if (value == null)
                return;
            (Tests = Tests ?? new List<string>(value.Length)).AddRange(value);
        }
    }
}

Пример .Net fiddle, показывающий, что Tests выделяется только при необходимости.

person dbc    schedule 27.07.2017

Мы оказались здесь после поиска Google по той же проблеме. В итоге мы проверили Count == 0 после десериализации и вручную установили для свойства значение null;

...
var varMyDeserializedClass = MyXmlSerializer.Deserialize(new StringReader(myInput));
if (varMyDeserializedClass.ListProperty.Count == 0)
{
  varMyDeserializedClass.ListProperty = null;
}
...

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

person Itaca    schedule 18.05.2020

Когда вы применяете [System.Xml.Serialization.XmlElement(IsNullable = true)] к свойству, после десериализации список будет нулевым.

person Don Kedero    schedule 24.03.2021

Другая возможность — использовать магический суффикс Specified:

public bool TestsSpecified {get;set;}

Если у вас есть сериализованное поле/свойство XXX и логическое свойство XXXSpecified, то логическое свойство устанавливается в зависимости от того, было ли установлено основное поле/свойство.

person Nick Hounsome    schedule 27.03.2021