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

对于应用程序configuration,我经常会创build一个具有应用程序configuration值的configuration类,然后将其反序列化为要使用的对象。 configuration对象通常是数据绑定到用户界面控件,以便用户可以更改和保存configuration。 configuration类通常具有分配给属性的默认值,所以始终存在默认configuration。 这工作得很好。 我最近有一个情况,我有一个提供一些默认path信息的string列表。 而我所看到的让我意识到我并不完全知道如何在XML反序列化过程中将对象属性填充到对象中。

所以我创build了一个简单的例子来显示行为。 以下是一个简单的类,有一些代码默认的属性。

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

这里有一个main创build一个新的对象,将对象的状态打印到控制台,将其序列化(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 

我想我总是认为List属性将被设置而不是附加到。 有没有人有一个指向collections的反序列化过程? 我显然现在知道正确的search条件,因为我的尝试是空的。 我看到其他职位描述了我所看到的和他们自己实现序列化的方法。 我更多的是寻找一个指针来描述集合被反序列化时会发生什么,所以我可以向自己解释我所看到的。

许多序列化器(尽pipe不是全部)以这种方式工作是正确的。 Json.NET确实,它的JsonConverter.ReadJson方法实际上有一个Object existingValue正是这种情况。

我不知道有哪些文件详细说明了这些实现细节。 确定序列化程序是否使用预分配集合的最容易的方法是,而不是无条件地分配然后设置一个本身,就是使用ObservableCollection<T>进行实际testing,并在更改时附加debugging监听器:

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

我跑上面的testing,发现:

  1. XmlSerializer和Json.NET使用预先存在的集合(如果存在)。 (在Json.NET中,可以通过将JsonSerializerSettings.ObjectCreationHandling设置为Replace来进行控制)
  2. JavaScriptSerializerBinaryFormatterDataContractSerializer不要,总是自己分配集合。 对于后两者来说,这并不奇怪,因为它们都不会调用默认的构造函数 ,而是直接分配空的内存。

我不知道为什么情况1中的序列化程序如此行事。 也许他们的作者担心包含的类可能想在内部使用被反序列化的集合的子类 ,或者像我一样把观察者附加到可观察的集合上,所以决定尊重这个devise?

值得注意的是,对于所有的序列化程序(除了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个序列化器。