• .NET陷阱之一:IDeserializationCallback带来的问题


    代码中有一个类,其中包含一个字典(Dictionary<Key, Value>),本来想让前者实现IDeserializationCallback接口,以便在反序列化时根据字典的内容做一些初始化工作,结果循环字典元素的代码就是不走。费了好大劲才找到原因,先来看有问题的代码:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.IO;
     4 using System.Runtime.Serialization;
     5 using System.Runtime.Serialization.Formatters.Binary;
     6 
     7 namespace DotNetBugs
     8 {
     9     [Serializable]
    10     public class Example : IDeserializationCallback
    11     {
    12         private Dictionary<string, string> map = new Dictionary<string, string>();
    13 
    14         public Example()
    15         {
    16             map.Add("one", "1");
    17             map.Add("two", "2");
    18         }
    19 
    20         public void OnDeserialization(object sender)
    21         {
    22             Dump();
    23         }
    24 
    25         public void Dump()
    26         {
    27             foreach (var item in map)
    28             {
    29                 Console.WriteLine(item.Key + " -> " + item.Value);
    30             }
    31         }
    32     }
    33 
    34     public class Starter
    35     {
    36         public static void Main(string[] args)
    37         {
    38             using (var stream = new MemoryStream())
    39             {
    40                 var formatter = new BinaryFormatter();
    41                 formatter.Serialize(stream, new Example());
    42 
    43                 stream.Seek(0, SeekOrigin.Begin);
    44                 var example = (Example)formatter.Deserialize(stream);
    45 
    46                 Console.WriteLine("after deserialize");
    47                 example.Dump();
    48 
    49                 Console.Read();
    50             }
    51         }
    52     }
    53 }

    你期望控制台有什么样的输出呢,是不是这样?

    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反编译得到,代码已经简化,只显示与此问题相关的部分 ):

     1 [Serializable]
     2 public class Dictionary<TKey, TValue> : ISerializable, IDeserializationCallback
     3 {
     4     private SerializationInfo m_siInfo;
     5     
     6     protected Dictionary(SerializationInfo info, StreamingContext context)
     7     {
     8         this.m_siInfo = info;
     9     }
    10     
    11     public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    12     {
    13         info.AddValue("Version", this.version);
    14         info.AddValue("Comparer", this.comparer, typeof(IEqualityComparer<TKey>));
    15         info.AddValue("HashSize", (this.buckets == null) ? 0 : this.buckets.Length);
    16         if (this.buckets != null)
    17         {
    18             KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[this.Count];
    19             this.CopyTo(array, 0);
    20             info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[]));
    21         }
    22     }
    23 
    24     public virtual void OnDeserialization(object sender)
    25     {
    26         if (this.m_siInfo != null)
    27         {
    28             int num = this.m_siInfo.GetInt32("Version");
    29             int num2 = this.m_siInfo.GetInt32("HashSize");
    30             this.comparer = (IEqualityComparer<TKey>)this.m_siInfo.GetValue(
    "Comparer", typeof(IEqualityComparer<TKey>));
    31 if (num2 != 0) 32 { 33 this.buckets = new int[num2]; 34 for (int i = 0; i < this.buckets.Length; i++) 35 { 36 this.buckets[i] = -1; 37 } 38 this.entries = new Entry<TKey, TValue>[num2]; 39 this.freeList = -1; 40 KeyValuePair<TKey, TValue>[] pairArray = (KeyValuePair<TKey, TValue>[])
    this.m_siInfo.GetValue("KeyValuePairs",
    typeof(KeyValuePair<TKey, TValue>[])); 41 if (pairArray == null) 42 { 43 ThrowHelper.ThrowSerializationException(
    ExceptionResource.Serialization_MissingKeyValuePairs);
    44 } 45 for (int j = 0; j < pairArray.Length; j++) 46 { 47 if (pairArray[j].Key == null) 48 { 49 ThrowHelper.ThrowSerializationException(
    ExceptionResource.Serialization_NullKey);
    50 } 51 this.Insert(pairArray[j].Key, pairArray[j].Value, true); 52 } 53 } 54 else 55 { 56 this.buckets = null; 57 } 58 this.version = num; 59 this.m_siInfo = null; 60 } 61 } 62 } 63

    原来Dictionary<Key, Value>在内部是通过数组的形式将自己的内容序列化到流中的,它也实现了IDeserializationCallback接口,用于在反序列化时重新构建字典。

    问题就在这里——在Example类的对象被反序列化时,对象图中一共有两个实现IDeserializationCallback接口的对象,而且从结果来看,Example.map的OnDeserialization方法是在Example类对象之后被调用的,所以Example.OnDeserialization调用时map中还没有任何内容!

    所以要解决这一问题,我们需要将代码改成下面那样:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.IO;
     4 using System.Runtime.Serialization;
     5 using System.Runtime.Serialization.Formatters.Binary;
     6 
     7 namespace dotNetBugs
     8 {
     9     [Serializable]
    10     public class Example : ISerializable, IDeserializationCallback
    11     {
    12         [NonSerialized]
    13         private Dictionary<string, string> map = new Dictionary<string, string>();
    14 
    15         public Example()
    16         {
    17             map.Add("one", "1");
    18             map.Add("two", "2");
    19         }
    20 
    21         private Example(SerializationInfo info, StreamingContext context)
    22         {
    23             var keys = (string[])info.GetValue("MapKeys", typeof(object));
    24             var vals = (string[])info.GetValue("MapVals", typeof(object));
    25             map = new Dictionary<string, string>();
    26             for (int i = 0; i < keys.Length; ++i)
    27             {
    28                 map.Add(keys[i], vals[i]);
    29             }
    30         }
    31 
    32         public void OnDeserialization(object sender)
    33         {
    34             Dump();
    35         }
    36 
    37         public void Dump()
    38         {
    39             foreach (var item in map)
    40             {
    41                 Console.WriteLine(item.Key + " -> " + item.Value);
    42             }
    43         }
    44 
    45         public void GetObjectData(SerializationInfo info, StreamingContext context)
    46         {
    47             var keys = new string[map.Count];
    48             var vals = new string[map.Count];
    49             int i = 0;
    50             foreach (var item in map)
    51             {
    52                 keys[i] = item.Key;
    53                 vals[i] = item.Value;
    54                 ++i;
    55             }
    56 
    57             info.AddValue("MapKeys", keys);
    58             info.AddValue("MapVals", vals);
    59         }
    60     }
    61 
    62     public class Starter
    63     {
    64         public static void Main(string[] args)
    65         {
    66             using (var stream = new MemoryStream())
    67             {
    68                 var formatter = new BinaryFormatter();
    69                 formatter.Serialize(stream, new Example());
    70 
    71                 stream.Seek(0, SeekOrigin.Begin);
    72                 var example = (Example)formatter.Deserialize(stream);
    73 
    74                 Console.WriteLine("after deserialize");
    75                 example.Dump();
    76 
    77                 Console.Read();
    78             }
    79         }
    80     }
    81 }

    这样,输出就像预料的一样了。

    总结一下:如果某个类Outer实现了IDeserializationCallback接口,而且OnDeserialization方法中的逻辑依赖于Outer类的某个成员inner,则一定检查inner是否也实现了IDeserializationCallback接口,如果是就需要特殊处理它的序列化过程。

  • 相关阅读:
    游戏人生Silverlight(2) 趣味钢琴[Silverlight 2.0(c#)]
    稳扎稳打Silverlight(35) 3.0控件之ChildWindow, SaveFileDialog, HeaderedItemsControl, VirtualizingStackPanel
    Silverlight 3.0 Demo
    稳扎稳打Silverlight(34) 3.0控件之Frame, Page, Label, DescriptionViewer, ValidationSummary
    再接再厉VS 2008 sp1 + .NET 3.5 sp1系列文章索引
    稳扎稳打Silverlight(37) 3.0动画之Easing(缓动效果)
    返璞归真 asp.net mvc (1) 添加、查询、更新和删除的 Demo
    返璞归真 asp.net mvc (2) 路由(System.Web.Routing)
    游戏人生Silverlight(5) 星际竞技场[Silverlight 2.0(c#, Farseer Physics Engine)]
    稳扎稳打Silverlight(30) 2.0Tip/Trick之Silverlight.js, Silverlight.supportedUserAgent.js, 自定义启动界面, 响应鼠标滚轮事件
  • 原文地址:https://www.cnblogs.com/brucebi/p/2993968.html
Copyright © 2020-2023  润新知