Словарь по десериализации пуст

В настоящее время я пишу класс двунаправленной карты, и у меня возникли некоторые проблемы с сериализацией / десериализацией класса (вопрос внизу).

Вот части класса, которые актуальны.

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

Поскольку словари forward- и inverseMap содержат одинаковые данные, моя идея заключалась в том, чтобы сериализовать только один из них (forwardMap), а затем создать другой (inverseMap) из его данных по десериализации. Тем не менее, inverseMap не заполняется никакими данными в конструкторе десериализации. Кажется, словарь forwardMap полностью десериализуется только после того, как конструктор десериализации класса уже выполнен.

Есть идеи, как это исправить?

1 ответ

Решение

Я предполагаю, что вы используете BinaryFormatter,

BinaryFormatter это графизатор Вместо того чтобы хранить объекты в чистом дереве, им присваиваются временные идентификаторы объектов и они сохраняются по мере их появления. Таким образом, когда объект десериализован, не гарантируется, что все упомянутые объекты были ранее десериализованы. Таким образом, возможно, записи в вашем forwardMap еще не были заполнены.

Нормальный обходной путь должен добавить IDeserializationCallback логика для вашего класса, и построить свой inverseMap а также inverseInstance после того, как все было десериализовано в методе OnDeserialization. Но, Dictionary<TKey, TValue>также реализует IDeserializationCallback, которая вводит дополнительную проблему секвенирования: она не гарантированно была вызвана раньше, чем ваша. На эту тему Microsoft пишет:

Объекты восстанавливаются изнутри, и вызывающие методы во время десериализации могут иметь нежелательные побочные эффекты, поскольку вызываемые методы могут ссылаться на ссылки на объекты, которые не были десериализованы к моменту вызова. Если десериализованный класс реализует 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] метод вместо этого, если вы предпочитаете.)

Кстати, в этом сообщении в блоге утверждается, что OnDeserialization метод HashTable от конструктора десериализации содержащего класса, а не от OnDeserialization так что вы можете попробовать.

Другие вопросы по тегам