使用代码默认值反序列化集合属性的 XML

4

在应用程序配置方面,我经常会创建一个配置类,其中包含应用程序的配置值,然后将其反序列化为对象以利用。通常,配置对象会绑定到用户界面控件,以便用户可以更改和持久化配置。配置类通常会为属性分配默认值,以便始终有一个默认配置。这一直运作得很好。最近,我遇到了一个情况,其中我有一个字符串列表,提供了一些默认路径信息。我看到了一些东西,让我意识到我并不完全知道对象属性是如何在XML反序列化为对象期间被填充的。

因此,我创建了一个简单的示例来展示这种行为。以下是一个简单的类,其中包含一些代码默认值的属性:

[Serializable]
public class TestConfiguration
   {
      public String Name 
      { 
         get
         {
            return mName;
         }
         set
         {
            mName = value;
         }
      }private String mName = "Pete Sebeck";

  public List<String> Associates 
  { 
     get
     {
        return mAssociates;
     }
     set
     {
        mAssociates = value;
     }
  } private List<String> mAssociates = new List<string>() { "Jon", "Natalie" };

  public override String ToString()
  {
     StringBuilder buffer = new StringBuilder();
     buffer.AppendLine(String.Format("Name: {0}", Name));
     buffer.AppendLine("Associates:");
     foreach(String associate in mAssociates)
     {
        buffer.AppendLine(String.Format("\t{0}", associate));
     }
     return buffer.ToString();
  }
   }

以下是创建新对象的主要代码,它会将对象状态打印到控制台、将其序列化(xml)到文件中,然后从该文件重新构建对象,并再次将对象状态打印到控制台。我期望得到的是与序列化内容匹配的对象,但实际上得到的是默认对象并添加了序列化列表的内容。

  static void Main(string[] args)
  {
     // Create a default object
     TestConfiguration configuration = new TestConfiguration();
     Console.WriteLine(configuration.ToString());

     // Serialize the object
     XmlSerializer writer = new XmlSerializer(typeof(TestConfiguration));
     StreamWriter filewriter = new StreamWriter("TestConfiguration.xml");
     writer.Serialize(filewriter, configuration);
     filewriter.Close();

     // Now deserialize the xml into another object
     XmlSerializer reader = new XmlSerializer(typeof(TestConfiguration));
     StreamReader filereader = new StreamReader("TestConfiguration.xml");
     TestConfiguration deserializedconfiguration = (TestConfiguration)reader.Deserialize(filereader);
     filereader.Close();

     Console.WriteLine(deserializedconfiguration.ToString());

     Console.ReadLine();
      }

结果:

Name: Pete Sebeck
Associates:
        Jon
        Natalie

Name: Pete Sebeck
Associates:
        Jon
        Natalie
        Jon
        Natalie

我猜想列表属性应该是被设置而不是追加的。有人能指导一下集合反序列化的过程吗?我的搜索似乎没有找到正确的术语,因为我的尝试都失败了。我看到其他帖子描述了我所看到的,并且他们的方法是自己实现序列化。我更多地寻找一个指针,描述集合反序列化时发生的事情,以便我可以向自己解释我所看到的。

1个回答

3
您是正确的,许多序列化器(但不是全部)都是这样工作的。Json.NET就是这样做的,它的JsonConverter.ReadJson方法实际上有一个Object existingValue来处理这种情况。
我不知道是否有任何文件详细说明这些实现细节。确定序列化器是否在存在时使用预分配的集合而不是无条件地分配并自行设置一个集合的最简单方法是通过使用ObservableCollection<T>进行测试,并在更改时附加调试侦听器。
[Serializable]
[DataContract]
public class TestConfiguration
{
    [DataMember]
    public String Name { get { return mName; } set { mName = value; } }

    private String mName = "Pete Sebeck";

    [DataMember]
    public ObservableCollection<String> Associates
    {
        get
        {
            Debug.WriteLine(mAssociates == null ? "Associates gotten, null value" : "Associates gotten, count = " + mAssociates.Count.ToString());
            return mAssociates;
        }
        set
        {
            Debug.WriteLine(value == null ? "Associates set to a null value" : "Associates set, count = " + value.Count.ToString());
            RemoveListeners(mAssociates);
            mAssociates = AddListeners(value);
        }
    }

    private ObservableCollection<String> mAssociates = AddListeners(new ObservableCollection<string>() { "Jon", "Natalie" });

    public override String ToString()
    {
        StringBuilder buffer = new StringBuilder();
        buffer.AppendLine(String.Format("Name: {0}", Name));
        buffer.AppendLine("Associates:");
        foreach (String associate in mAssociates)
        {
            buffer.AppendLine(String.Format("\t{0}", associate));
        }
        return buffer.ToString();
    }

    static ObservableCollection<String> AddListeners(ObservableCollection<String> list)
    {
        if (list != null)
        {
            list.CollectionChanged -= list_CollectionChanged; // In case it was already there.
            list.CollectionChanged += list_CollectionChanged;
        }
        return list;
    }

    static ObservableCollection<String> RemoveListeners(ObservableCollection<String> list)
    {
        if (list != null)
        {
            list.CollectionChanged -= list_CollectionChanged; // In case it was already there.
        }
        return list;
    }

    public static ValueWrapper<bool> ShowDebugInformation = new ValueWrapper<bool>(false);

    static void list_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (!ShowDebugInformation)
            return;
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                Debug.WriteLine(string.Format("Added {0} items", e.NewItems.Count));
                break;
            case NotifyCollectionChangedAction.Move:
                Debug.WriteLine("Moved items");
                break;
            case NotifyCollectionChangedAction.Remove:
                Debug.WriteLine(string.Format("Removed {0} items", e.OldItems.Count));
                break;
            case NotifyCollectionChangedAction.Replace:
                Debug.WriteLine("Replaced items");
                break;
            case NotifyCollectionChangedAction.Reset:
                Debug.WriteLine("Reset collection");
                break;
        }
    }
}

public static class TestTestConfiguration
{
    public static void Test()
    {
        var test = new TestConfiguration();

        Debug.WriteLine("\nTesting Xmlserializer...");
        var xml = XmlSerializationHelper.GetXml(test);
        using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
        {
            var testFromXml = XmlSerializationHelper.LoadFromXML<TestConfiguration>(xml);
            Debug.WriteLine("XmlSerializer result: " + testFromXml.ToString());
        }

        Debug.WriteLine("\nTesting Json.NET...");
        var json = JsonConvert.SerializeObject(test, Formatting.Indented);
        using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
        {
            var testFromJson = JsonConvert.DeserializeObject<TestConfiguration>(json);
            Debug.WriteLine("Json.NET result: " + testFromJson.ToString());
        }

        Debug.WriteLine("\nTesting DataContractSerializer...");
        var contractXml = DataContractSerializerHelper.GetXml(test);
        using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
        {
            var testFromContractXml = DataContractSerializerHelper.LoadFromXML<TestConfiguration>(contractXml);
            Debug.WriteLine("DataContractSerializer result: " + testFromContractXml.ToString());
        }

        Debug.WriteLine("\nTesting BinaryFormatter...");
        var binary = BinaryFormatterHelper.ToBase64String(test);
        using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
        {
            var testFromBinary = BinaryFormatterHelper.FromBase64String<TestConfiguration>(binary);
            Debug.WriteLine("BinaryFormatter result: " + testFromBinary.ToString());
        }

        Debug.WriteLine("\nTesting JavaScriptSerializer...");
        var javaScript = new JavaScriptSerializer().Serialize(test);
        using (new SetValue<bool>(TestConfiguration.ShowDebugInformation, true))
        {
            var testFromJavaScript = new JavaScriptSerializer().Deserialize<TestConfiguration>(javaScript);
            Debug.WriteLine("JavaScriptSerializer result: " + testFromJavaScript.ToString());
        }
    }
}

我运行了上面的测试,并发现:
  1. XmlSerializer 和 Json.NET 会使用已存在的集合。 (在 Json.NET 中,可以通过将 JsonSerializerSettings.ObjectCreationHandling 设置为 Replace 来控制这一点)
  2. JavaScriptSerializerBinaryFormatterDataContractSerializer 不会使用已存在的集合,而总是自己分配集合。对于后两者来说,这并不奇怪,因为它们都不调用默认构造函数,而是直接分配空内存。

我不知道为什么情况1中的序列化程序会以这种方式行事。也许它们的作者担心包含类可能希望在反序列化时使用集合的子类,或者像我一样向可观察集合附加观察器,因此决定遵守该设计?

注意:对于所有序列化程序(除了可能是BinaryFormatter,我不确定),如果一个集合属性被明确声明为数组,则序列化程序将分配数组并在完全填充后设置数组。这意味着数组始终可以在序列化期间用作代理集合
通过使用代理数组,您可以保证在反序列化期间覆盖您的集合:
    [IgnoreDataMember]
    [XmlIgnore]
    [ScriptIgnore]
    public ObservableCollection<String> { get; set; } // Or List<string> or etc.

    [XmlArray("Associates")]
    [DataMember(Name="Associates")]
    public string[] AssociateArray
    {
        get
        {
            return (Associates == null ? null : Associates.ToArray());
        }
        set
        {
            if (Associates == null)
                Associates = new ObservableCollection<string>();
            Associates.Clear();
            if (value != null)
                foreach (var item in value)
                    Associates.Add(item);
        }
    }

现在集合只返回所有5个序列化器中先前序列化的成员。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接