反序列化时字典为空

本文关键字:字典 反序列化 | 更新日期: 2023-09-27 18:20:01

我目前正在编写一个双向映射类,并且在该类的序列化/反序列化方面遇到了一些问题(底部的问题)。

以下是课程中相关的部分。

/// <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);
        }
 }

由于正向和反向映射字典包含完全相同的数据,我的想法是只序列化其中一个(forwardMap),然后在反序列化时根据其数据构建另一个(inverteMap)。但是,inverteMap在反序列化构造函数中没有填充任何数据。forwardMap字典似乎是在类的反序列化构造函数已经执行之后才完全反序列化的。

你知道怎么解决这个问题吗?

反序列化时字典为空

我假设您使用的是BinaryFormatter

BinaryFormatter是一个图形序列化程序。对象不是存储在纯树中,而是被分配临时对象id,并在遇到时存储。因此,当一个对象被反序列化时,不能保证所有引用的对象都已被反序列化。因此,您的forwardMap中的条目可能尚未填写。

通常的解决方法是将IDeserializationCallback逻辑添加到类中,并在OnDeserialization方法中对所有内容进行反序列化后构建inverseMapinverseInstance。但是,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.
    

    也就是说,在回调中,在使用嵌套字典之前调用它的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);
        }
    

    (如果你愿意,你可以用[OnDeserialied]的方法。)

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