代碼中有一個類,其中包含一個字典(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