XmlSerializer и List <T> со значениями по умолчанию

Я наблюдал странное поведение при сериализации и десериализации класса, который имеет член типа List<T>, который был заполнен значениями по умолчанию во время построения. В отличие от свойства массива свойство типа List<T> не будет опустошено при десериализации с помощью XmlSerializer.

Вот мой код:

public class Program
{
    public class Config
    {
        public Config()
        {
            Test1 = new List<string>()  {"A", "B"};
            Test2 = new String[] {"A", "B"};
        }
        public List<string> Test1 {get;set;}
        public string[] Test2 {get;set;}
    }

    public static void Main()
    {
        XmlSerializer xmlSerializer =
            new XmlSerializer(typeof(Config));
        using(Stream s = new MemoryStream())
        {
            xmlSerializer.Serialize(s, new Config());
            s.Position = 0;
            xmlSerializer.Serialize(Console.Out,
                xmlSerializer.Deserialize(s));
        }
    }
}

И это результат:

<?xml version="1.0" encoding="ibm850"?>
<Config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Test1>
    <string>A</string>
    <string>B</string>
    <string>A</string>
    <string>B</string>
  </Test1>
  <Test2>
    <string>A</string>
    <string>B</string>
  </Test2>
</Config>

Почему List<T> обрабатывается по-разному с помощью XmlSerializer, чем массив, и что я могу сделать, чтобы изменить это поведение?

Ответ 1

Интересно; Я никогда не замечал этого в прошлом, но он определенно воспроизводимый. Поскольку XmlSerializer не поддерживает обратные вызовы сериализации (чтобы вы знали, что он работает для сериализации), это трудно повлиять; возможно, самым простым ответом является "не помещать данные по умолчанию в объекты в конструкторе" (хотя, возможно, предлагает метод factory, который это делает).

Вы можете попробовать реализовать IXmlSerializable, но это слишком сложно, даже для простого примера.

Я проверил, хотя, и DataContractSerializer не ведет себя так, чтобы вы могли перейти на DataContractSerializer; здесь мой тестовый код с DCS:

DataContractSerializer ser =
    new DataContractSerializer(typeof(Config));
using (Stream s = new MemoryStream())
{
    ser.WriteObject(s, new Config());
    s.Position = 0;
    using(var writer = XmlWriter.Create(Console.Out)) {
        ser.WriteObject(writer, ser.ReadObject(s));
    }
}

и вот что я подразумеваю под методом factory:

public class Config
{
    public Config()
    {
        Test1 = new List<string>();
        Test2 = nix;
    }
    public List<string> Test1 { get; set; }
    public string[] Test2 { get; set; }

    private static readonly string[] nix = new string[0];
    public static Config CreateDefault()
    {
        Config config = new Config();
        config.Test1.Add("A");
        config.Test1.Add("B");
        config.Test2 = new string[2] { "A", "B" };
        return config;
    }
}

Ответ 2

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

Мое обходное решение состояло в том, чтобы установить XMLIgnoreAttribute в List и включить публичный член массива типа объекта, когда set/get обрабатывает совокупность списка из массива.

Что-то вроде следующего позволяет создавать значения по умолчанию в конструкторе, но не позволяет сериализатору XML добавлять записи в список по умолчанию. (ошибка/nul валидации в стороне).

public class Config
{
    public Config()
    {
        Test1 = new List<string>() { "A", "B" }; 
        Test2 = new String[] { "A", "B" };

    }

    [XmlIgnore]
    public List<string> Test1 { get; set; }
    public string[] Test2 { get; set; }

    // This member is only to be used during XML serialization
    public string[] Test1_Array 
    { 
        get
        {
            return Test1.ToArray();
        }
        set 
        {
            Test1 = value.ToList();
        }
    }        

}