Dictionary is empty on deserialization

余生颓废 提交于 2019-11-28 13:44:57

I am assuming you are using BinaryFormatter.

BinaryFormatter is a graph serializer. Rather than objects being stored in a pure tree, they are assigned temporary object ids and stored as they are encountered. Thus when an object is deserialized, it is not guaranteed that all referenced objects have been previously deserialized. Thus it's possible the entries in your forwardMap have not been filled in yet.

The normal workaround is to add IDeserializationCallback logic to your class, and build your inverseMap and inverseInstance after everything has been deserialized in the OnDeserialization method. But, Dictionary<TKey, TValue> also implements IDeserializationCallback, which introduces an additional sequencing problem: it is not guaranteed to have been called before yours is. On this topic, Microsoft writes:

Objects are reconstructed from the inside out, and calling methods during deserialization can have undesirable side effects, since the methods called might refer to object references that have not been deserialized by the time the call is made. If the class being deserialized implements the IDeserializationCallback, the OnSerialization method will automatically be called when the entire object graph has been deserialized. At this point, all the child objects referenced have been fully restored. A hash table is a typical example of a class that is difficult to deserialize without using the event listener described above. It is easy to retrieve the key/value pairs during deserialization, but adding these objects back to the hash table can cause problems since there is no guarantee that classes that derived from the hash table have been deserialized. Calling methods on a hash table at this stage is therefore not advisable.

Thus there are a couple things you could do:

  1. Rather than storing a Dictionary<TKey,TValue>, store an array of KeyValuePair<TKey,TValue>. This has the advantage of making your binary data simpler but does require you to allocate the array in your GetObjectData() method.

  2. Or follow the advice in the dictionary reference source:

    // It might be necessary to call OnDeserialization from a container if the container object also implements
    // OnDeserialization. However, remoting will call OnDeserialization again.
    // We can return immediately if this function is called twice. 
    // Note we set remove the serialization info from the table at the end of this method.
    

    I.e. in your callback, call the OnDeserialization method of your nested dictionary before using it:

    public partial class BidirectionalDictionary<TKey, TValue> : IDeserializationCallback
    {
        public void OnDeserialization(object sender)
        {
            this.forwardMap.OnDeserialization(sender);
            foreach (KeyValuePair<TKey, TValue> entry in forwardMap)
            {
                this.inverseMap.Add(entry.Value, entry.Key);
            }
            // inverseInstance will no longer be able to be read-only sicne it is being allocated in a post-deserialization callback.
            this.inverseInstance = new BidirectionalDictionary<TValue, TKey>(this);
        }
    

    (You could do it in an [OnDeserialied] method instead if you prefer.)

Incidentally, this blog post claims that it is safe to call the OnDeserialization method of a HashTable from the deserialization constructor of a containing class, rather than later from OnDeserialization, so you might give that a try.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!