反序列化时字典为空

Hen*_*rik 5 c# serialization binaryformatter deserialization

我目前正在编写一个双向地图类,我在类的序列化/反序列化方面遇到了一些麻烦(问题在底部).

这是相关课程的部分内容.

/// <summary>
/// Represents a dictionary where both keys and values are unique, and the mapping between them is bidirectional.
/// </summary>
/// <typeparam name="TKey"> The type of the keys in the dictionary. </typeparam>
/// <typeparam name="TValue"> The type of the values in the dictionary. </typeparam>
[Serializable]
public class BidirectionalDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IEquatable<BidirectionalDictionary<TKey, TValue>>, ISerializable, IDeserializationCallback
{

        /// <summary>
        /// A dictionary that maps the keys to values.
        /// </summary>
        private readonly Dictionary<TKey, TValue> forwardMap;

        /// <summary>
        /// A dictionary that maps the values to keys.
        /// </summary>
        private readonly Dictionary<TValue, TKey> inverseMap;

        /// <summary>
        /// An instance of the dictionary where the values are the keys, and the keys are the values. 
        /// </summary>
        private readonly BidirectionalDictionary<TValue, TKey> inverseInstance;

        /// <summary>
        /// Initializes a new instance of the dictionary class with serialized data. </summary>
        /// </summary>
        /// <param name="info"> The serialization info. </param>
        /// <param name="context">  The sserialization context. </param>
        protected BidirectionalDictionary(SerializationInfo info, StreamingContext context)
        {
            this.forwardMap = (Dictionary<TKey, TValue>)info.GetValue("UnderlyingDictionary", typeof(Dictionary<TKey, TValue>));
            this.inverseMap = new Dictionary<TValue, TKey>(
                forwardMap.Count,
                (IEqualityComparer<TValue>)info.GetValue("InverseComparer", typeof(IEqualityComparer<TValue>)));

            // forwardMap is always empty at this point.
            foreach (KeyValuePair<TKey, TValue> entry in forwardMap)
                inverseMap.Add(entry.Value, entry.Key);

            this.inverseInstance = new BidirectionalDictionary<TValue, TKey>(this);
        }

        /// <summary>
        /// Gets the data needed to serialize the dictionary.
        /// </summary>
        /// <param name="info"> The serialization info. </param>
        /// <param name="context">  The serialization context. </param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("UnderlyingDictionary", forwardMap);
            info.AddValue("InverseComparer", inverseMap.Comparer);
        }
 }
Run Code Online (Sandbox Code Playgroud)

由于forwardMap和inverseMap字典包含完全相同的数据,我的想法是仅序列化其中一个(forwardMap),然后根据反序列化的数据构建另一个(inverseMap).但是,反向映射不会在反序列化构造函数中填充任何数据.看来,在已经执行了类的反序列化构造函数之后,forwardMap字典才被完全反序列化.

有关如何解决此问题的任何想法?

dbc*_*dbc 7

我假设你正在使用BinaryFormatter.

BinaryFormatter是一个图形序列化器.它们不是存储在纯树中的对象,而是分配临时对象ID并在遇到它们时进行存储.因此,当反序列化对象时,不能保证所有引用的对象先前已被反序列化.因此,您的条目可能forwardMap尚未填写.

正常的解决方法是添加IDeserializationCallback逻辑类,并建立您inverseMapinverseInstance后一切都已经在反序列化OnDeserialization方法.但是,Dictionary<TKey, TValue>也是实现IDeserializationCallback,它引入了一个额外的排序问题:它不能保证在你之前被调用.在这个主题上,微软写道:

从内到外重建对象,并且在反序列化期间调用方法可能具有不期望的副作用,因为调用的方法可能引用在进行调用时尚未反序列化的对象引用.如果被反序列化的类实现了IDeserializationCallback,则在对整个对象图进行反序列化时,将自动调用OnSerialization方法.此时,引用的所有子对象都已完全恢复.哈希表是不使用上述事件侦听器而难以反序列化的类的典型示例.在反序列化期间很容易检索键/值对,但是将这些对象添加回哈希表会导致问题,因为无法保证从哈希表派生的类已被反序列化.因此,不建议在此阶段调用哈希表上的方法.

因此,您可以做几件事:

  1. 而不是存储Dictionary<TKey,TValue>,存储数组KeyValuePair<TKey,TValue>.这样做的好处是可以使二进制数据更简单,但需要在GetObjectData()方法中分配数组.

  2. 或者按照字典参考源中的建议:

    // 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.
    
    Run Code Online (Sandbox Code Playgroud)

    即在回调中,OnDeserialization在使用之前调用嵌套字典的方法:

    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);
        }
    
    Run Code Online (Sandbox Code Playgroud)

    ([OnDeserialied]如果您愿意,可以在方法中完成.)

顺便说一句,这篇博文称,从包含类的反序列化构造函数调用a的OnDeserialization方法是安全的,而不是稍后调用,所以你可以尝试一下.HashTableOnDeserialization