您是对的,许多序列化器(尽管不是全部)都以这种方式工作。 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());
}
}
}
我进行了上面的测试,发现:
-
XmlSerializer
Json.NET 使用预先存在的集合(如果存在)。 (在 Json.NET 中,这可以通过设置来控制JsonSerializerSettings.ObjectCreationHandling to Replace)
-
JavaScriptSerializer
, BinaryFormatter
and DataContractSerializer
不,并且总是自己分配集合。对于后两者来说,这并不奇怪,因为两者不调用默认构造函数而是直接分配空内存。
我不知道为什么情况 1 中的序列化器会这样。也许他们的作者担心包含类可能想要在内部使用subclass集合被反序列化,或者像我所做的那样将观察者附加到可观察集合,因此决定尊重该设计?
一个注释 - 对于所有序列化器(也许除了,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 个序列化程序。