代碼中有一個類,其中包含一個字典(Dictionary<Key, Value>),本來想讓前者實現IDeserializationCallback接口,以便在反序列化時根據字典的內容做一些初始化工作,結果循環字典元素的代碼就是不走。費了好大勁才找到原因,先來看有問題的代碼:
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; namespace DotNetBugs { [Serializable] public class Example : IDeserializationCallback { private Dictionary<string, string> map = new Dictionary<string, string>(); public Example() { map.Add("one", "1"); map.Add("two", "2"); } public void OnDeserialization(object sender) { Dump(); } public void Dump() { foreach (var item in map) { Console.WriteLine(item.Key + " -> " + item.Value); } } } public class Starter { public static void Main(string[] args) { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, new Example()); stream.Seek(0, SeekOrigin.Begin); var example = (Example)formatter.Deserialize(stream); Console.WriteLine("after deserialize"); example.Dump(); Console.Read(); } } } }
你期望控制台有什麼樣的輸出呢,是不是這樣?
one -> 1 | two -> 2 | 在第44行反序列化時,Example.OnDeserialization中調用Dump的輸出。 after deserialize one -> 1 | two -> 2 | 在第47行調用Dump的輸出
但實際的輸出內容是:
after deserialize one -> 1 two -> 2
為什麼會這樣呢?
來看一下Dictionary<Key, Value>的源代碼(通過.NET Reflector反編譯得到,代碼已經簡化,只顯示與此問題相關的部分 ):
[Serializable] public class Dictionary<TKey, TValue> : ISerializable, IDeserializationCallback { private SerializationInfo m_siInfo; protected Dictionary(SerializationInfo info, StreamingContext context) { this.m_siInfo = info; } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Version", this.version); info.AddValue("Comparer", this.comparer, typeof(IEqualityComparer<TKey>)); info.AddValue("HashSize", (this.buckets == null) ? 0 : this.buckets.Length); if (this.buckets != null) { KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[this.Count]; this.CopyTo(array, 0); info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[])); } } public virtual void OnDeserialization(object sender) { if (this.m_siInfo != null) { int num = this.m_siInfo.GetInt32("Version"); int num2 = this.m_siInfo.GetInt32("HashSize"); this.comparer = (IEqualityComparer<TKey>)this.m_siInfo.GetValue( "Comparer", typeof(IEqualityComparer<TKey>)); if (num2 != 0) { this.buckets = new int[num2]; for (int i = 0; i < this.buckets.Length; i++) { this.buckets[i] = -1; } this.entries = new Entry<TKey, TValue>[num2]; this.freeList = -1; KeyValuePair<TKey, TValue>[] pairArray = (KeyValuePair<TKey, TValue>[]) this.m_siInfo.GetValue("KeyValuePairs", typeof(KeyValuePair<TKey, TValue>[])); if (pairArray == null) { ThrowHelper.ThrowSerializationException( ExceptionResource.Serialization_MissingKeyValuePairs); } for (int j = 0; j < pairArray.Length; j++) { if (pairArray[j].Key == null) { ThrowHelper.ThrowSerializationException( ExceptionResource.Serialization_NullKey); } this.Insert(pairArray[j].Key, pairArray[j].Value, true); } } else { this.buckets = null; } this.version = num; this.m_siInfo = null; } } }
原來Dictionary<Key, Value>在內部是通過數組的形式將自己的內容序列化到流中的,它也實現了IDeserializationCallback接口,用於在反序列化時重新構建字典。
問題就在這裡——在Example類的對象被反序列化時,對象圖中一共有兩個實現IDeserializationCallback接口的對象,而且從結果來看,Example.map的OnDeserialization方法是在Example類對象之後被調用的,所以Example.OnDeserialization調用時map中還沒有任何內容!
所以要解決這一問題,我們需要將代碼改成下面那樣:
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; namespace dotNetBugs { [Serializable] public class Example : ISerializable, IDeserializationCallback { [NonSerialized] private Dictionary<string, string> map = new Dictionary<string, string>(); public Example() { map.Add("one", "1"); map.Add("two", "2"); } private Example(SerializationInfo info, StreamingContext context) { var keys = (string[])info.GetValue("MapKeys", typeof(object)); var vals = (string[])info.GetValue("MapVals", typeof(object)); map = new Dictionary<string, string>(); for (int i = 0; i < keys.Length; ++i) { map.Add(keys[i], vals[i]); } } public void OnDeserialization(object sender) { Dump(); } public void Dump() { foreach (var item in map) { Console.WriteLine(item.Key + " -> " + item.Value); } } public void GetObjectData(SerializationInfo info, StreamingContext context) { var keys = new string[map.Count]; var vals = new string[map.Count]; int i = 0; foreach (var item in map) { keys[i] = item.Key; vals[i] = item.Value; ++i; } info.AddValue("MapKeys", keys); info.AddValue("MapVals", vals); } } public class Starter { public static void Main(string[] args) { using (var stream = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, new Example()); stream.Seek(0, SeekOrigin.Begin); var example = (Example)formatter.Deserialize(stream); Console.WriteLine("after deserialize"); example.Dump(); Console.Read(); } } } }
這樣,輸出就像預料的一樣了。
總結一下:如果某個類Outer實現了IDeserializationCallback接口,而且OnDeserialization方法中的邏輯依賴於Outer類的某個成員inner,則一定檢查inner是否也實現了IDeserializationCallback接口,如果是就需要特殊處理它的序列化過程。
出處:http://www.cnblogs.com/brucebi/archive/2013/04/01/2993968.html