2014-08-30 17 views
5

Obecnie piszę dwukierunkową klasę mapy i mam pewne problemy z serializacją/deserializacją klasy (pytanie na dole).Słownik jest pusty przy deserializacji

Oto części klasy, które są istotne.

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

Od wybiegające i inverseMap słowniki zawierają te same dane, mój pomysł był do serializacji tylko jeden z nich (forwardMap), a następnie zbudować drugi (inverseMap) z niego dane o deserializacji. Jednak inverseMap nie zapełnia się żadnymi danymi w konstruktorze deserializacji. Wygląda na to, że słownik forwardMap jest całkowicie deserializowany dopiero po wykonaniu konstruktora deserializacji klasy.

Każdy pomysł, jak to naprawić?

+0

tylko po to, aby potwierdzić, czy używasz ['BinaryFormatter'] (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter%28v=vs.110% 29.aspx)? – dbc

+0

Czy możesz napisać pełny kod, abyśmy mogli przetestować go bezpośrednio? –

+0

Zamierzasz edytować i opublikować pełny kod, ale problem jest dokładnie taki, jak wyjaśniono poniżej @dbc. Więc tak, używam BinaryFormatter. – Henrik

Odpowiedz

7

Zakładam, że używasz BinaryFormatter.

BinaryFormatter to serializator wykresów. Zamiast obiektów przechowywanych w czystym drzewie, są one przypisywane identyfikatorom obiektów tymczasowych i przechowywane, gdy są napotykane. Tak więc, gdy obiekt jest deserializowany, nie ma gwarancji, że wszystkie obiekty, do których istnieje odwołanie, zostały wcześniej deserializowane. Dlatego możliwe jest, że wpisy w twoim numerze forwardMap nie zostały jeszcze wypełnione.

Normalne obejście jest dodanie IDeserializationCallback logikę do klasy i budować swoją inverseMap i inverseInstance po wszystko zostało rozszeregować w metodzie OnDeserialization. Ale, Dictionary<TKey, TValue> implementuje także IDeserializationCallback, co wprowadza dodatkowy problem z sekwencjonowaniem: nie można zagwarantować, że został on wywołany przed twoją. Na ten temat, Microsoft writes:

Przedmioty są rekonstruowane od wewnątrz na zewnątrz, a wywołanie metody podczas deserializacji może mieć niepożądane skutki uboczne, ponieważ metody zwane może odnosić się do obiektów odniesienia, które nie zostały rozszeregować do czasu połączenia jest zrobione. Jeśli klasa jest deserialized implementuje IDeserializationCallback, metoda OnSerialization zostanie automatycznie wywołana, gdy cały wykres obiektów zostanie deserialized. W tym momencie wszystkie przywołane obiekty podrzędne zostały w pełni przywrócone. Tabela mieszająca jest typowym przykładem klasy, która jest trudna do deserializacji bez użycia detektora zdarzeń opisanego powyżej. Łatwo jest odzyskać pary klucz/wartość podczas deserializacji, ale dodanie tych obiektów z powrotem do tabeli mieszającej może spowodować problemy, ponieważ nie ma gwarancji, że klasy, które pochodzą z tabeli mieszania, zostały przekształcone w postać szeregową. Na tym etapie wywoływanie metod na tablicy mieszającej nie jest zalecane.

Zatem istnieje kilka rzeczy można zrobić:

  1. Zamiast przechowywania Dictionary<TKey,TValue>, przechowywać tablicę KeyValuePair<TKey,TValue>. Ma to tę zaletę, że upraszcza twoje dane binarne, ale wymaga przydzielenia tablicy w metodzie GetObjectData().

  2. Albo stosować się do zaleceń w 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. 
    

    Tjw zwrotnego, należy wywołać metodę swojego zagnieżdżonego słownika OnDeserialization przed użyciem go:

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

    (. Można to zrobić w [OnDeserialied] metody zamiast jeśli wolisz)

notabene this blog post twierdzi, że można bezpiecznie wywoływać metodę OnDeserialization z konstruktora deserializacji klasy zawierającej, a nie później niż OnDeserialization, więc możesz spróbować.

+0

To była dokładnie przyczyna problemu. Rzeczywiście wypróbowałem aplikację IDeserializationCallback, ale oczywiście nie zadziałało (ponieważ słownik również ma swój własny oddzwanianie). Teraz to wszystko ma sens. – Henrik