2014-08-30 3 views
5

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

Вот некоторые части этого класса.

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

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

Любая идея о том, как исправить это?

+0

просто для подтверждения, используете ли вы ['BinaryFormatter'] (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter%28v=vs.110% 29.aspx)? – dbc

+0

Не могли бы вы разместить полный код, чтобы мы могли проверить его напрямую? –

+0

Собирался редактировать и публиковать полный код, но проблема в том, что объясняет @dbc ниже. Так что да, я использую BinaryFormatter. – Henrik

ответ

7

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

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

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

объектов восстанавливаются изнутри, и вызов методов во время десериализации может иметь нежелательные побочные эффекты, так как методы, называемые могут относиться к ссылкам на объектах, которые не были десериализованными к тому времени вызова сделан. Если десериализованный класс реализует IDeserializationCallback, метод OnSerialization будет автоматически вызываться, когда весь объектный граф будет десериализован. На этом этапе все дочерние объекты, на которые делается ссылка, были полностью восстановлены. Хэш-таблица является типичным примером класса, который трудно десериализовать без использования прослушивателя событий, описанного выше. Легко получить пары ключ/значение во время десериализации, но добавление этих объектов обратно в хеш-таблицу может вызвать проблемы, поскольку нет гарантии, что классы, полученные из хеш-таблицы, были десериализованы. Поэтому методы вызова в хеш-таблице на этом этапе нецелесообразны.

Таким образом, есть пара вещей, которые вы могли бы сделать:

  1. Вместо хранения Dictionary<TKey,TValue>, хранить массив KeyValuePair<TKey,TValue>. Это имеет преимущество, заключающееся в том, что ваши двоичные данные проще, но вам требуется выделить массив в вашем методе GetObjectData().

  2. Или следовать советам в 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. 
    

    Т.е.в обратном вызове, вызовите 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] методы вместо этого, если вы предпочитаете)

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

+0

Именно это и было причиной проблемы. Я действительно попытался выполнить IDeserializationCallback, но, очевидно, это не сработало (поскольку словарь также имеет собственный обратный вызов). Теперь все имеет смысл. – Henrik

Смежные вопросы