2014-08-30 13 views
5

Attualmente sto scrivendo una classe di mappe bidirezionali e sto avendo qualche problema con la serializzazione/deserializzazione della classe (domanda in fondo).Il dizionario è vuoto per la deserializzazione

Ecco le parti della classe che è rilevante.

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

Dal momento che i dizionari forward e inverseMap contengono gli stessi dati esatti, la mia idea era quella di serializzare solo uno di essi (forwardMap), e poi costruire l'altra (inverseMap) da esso è i dati sulla deserializzazione. Tuttavia, inverseMap non viene popolato con alcun dato nel costruttore di deserializzazione. Sembra che il dizionario forwardMap sia completamente deserializzato dopo che il costruttore di deserializzazione della classe è già stato eseguito.

Qualche idea su come risolvere questo problema?

+0

solo per confermare, stai usando ['BinaryFormatter'] (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter%28v=vs.110% 29.aspx)? – dbc

+0

Potresti per favore postare il codice completo in modo che possiamo testarlo direttamente? –

+0

Stava per modificare e pubblicare il codice completo, ma il problema è esattamente quello che @dbc ha spiegato di seguito. Quindi sì, sto usando BinaryFormatter. – Henrik

risposta

7

Suppongo che si stia utilizzando BinaryFormatter.

BinaryFormatter è un serializzatore grafico. Anziché gli oggetti vengono archiviati in un albero puro, vengono assegnati gli ID oggetto temporanei e archiviati così come vengono rilevati. Pertanto, quando un oggetto viene deserializzato, non è garantito che tutti gli oggetti di riferimento siano stati precedentemente deserializzati. Pertanto è possibile che le voci nel tuo forwardMap non siano state ancora compilate.

La soluzione normale è quello di aggiungere IDeserializationCallback logica per la vostra classe, e costruire la vostra inverseMap e inverseInstance dopo tutto è stato deserializzato nel metodo OnDeserialization. Ma, Dictionary<TKey, TValue> implementa anche IDeserializationCallback, che introduce un ulteriore problema di sequenziamento: non è garantito che sia stato chiamato prima che sia il tuo. Su questo tema, Microsoft writes:

Gli oggetti vengono ricostruiti dall'interno verso l'esterno, e chiamare i metodi durante la deserializzazione possono avere effetti collaterali indesiderati, poiché i metodi chiamati potrebbero fare riferimento a riferimenti agli oggetti che non sono stati deserializzate dal momento in cui la chiamata è fatto. Se la classe che viene deserializzata implementa l'IDeserializationCallback, il metodo OnSerialization verrà chiamato automaticamente quando l'intero grafico dell'oggetto è stato deserializzato. A questo punto, tutti gli oggetti figlio referenziati sono stati completamente ripristinati. Una tabella hash è un tipico esempio di una classe che è difficile da deserializzare senza utilizzare il listener di eventi descritto sopra. È facile recuperare le coppie chiave/valore durante la deserializzazione, ma l'aggiunta di questi oggetti alla tabella hash può causare problemi poiché non vi è alcuna garanzia che le classi derivate dalla tabella hash siano state deserializzate. Pertanto, non è consigliabile chiamare i metodi su una tabella hash in questa fase.

Così ci sono un paio di cose che potreste fare:

  1. Invece di memorizzazione di un Dictionary<TKey,TValue>, negozio una serie di KeyValuePair<TKey,TValue>. Questo ha il vantaggio di semplificare i dati binari ma richiede di allocare la matrice nel metodo GetObjectData().

  2. O seguire il consiglio nella 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. 
    

    Vale a direnel vostro richiamata, chiamare il metodo del dizionario nidificato OnDeserialization prima di utilizzarlo:

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

    (. Si potrebbe fare in un metodo [OnDeserialied], invece, se si preferisce)

per inciso, this blog post affermazioni che è possibile chiamare il metodo OnDeserialization di del costruttore di deserializzazione di una classe contenente, anziché più tardi dallo OnDeserialization, quindi è possibile provarlo.

+0

Questa era esattamente la causa del problema. Ho effettivamente provato l'approcinio IDeserializationCallback, ma ovviamente non ha funzionato (dal momento che il dizionario ha anche il proprio callback). Ora tutto ha un senso. – Henrik