Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I have a general understanding on how ISerializable works e.g. how formatter constructs a SerializationInfo and pass it to the object (let's say Dictionary) and when the foramtter does deserialization, it retrieves SerializationInfo and calls Dictionary's specail protected constructor and pass this object etc. Below is some code(simplified by me) in regards to Dictionary:

[Serializable]
public class Dictionary<TKey,TValue>: ...ISerializable, IDeserializationCallback  {
   
   private SerializationInfo m_siInfo; // Only used for deserialization
   
   // Special constructor to control deserialization
   protected Dictionary(SerializationInfo info, StreamingContext context) {
      // During deserialization, save the SerializationInfo for OnDeserialization
      m_siInfo = info;
   }

   // implements ISerializable for serialization purpose
   public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
      info.AddValue("Version", m_version);
      info.AddValue("Comparer", m_comparer, typeof(IEqualityComparer<TKey>));
      info.AddValue("HashSize", (m_ buckets == null) ? 0 : m_buckets.Length);
      if (m_buckets != null) {
         KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[Count];
         CopyTo(array, 0);
        info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[]));
      }
   }

   // implement IDeserializationCallback, so this method will be called after deserialization, which set the dictionary internal state bakc to its original state before serialization
   public virtual void OnDeserialization(Object sender) {    
      if (m_siInfo == null) return; // Never set, return
      Int32 num = m_siInfo.GetInt32("Version");
      Int32 num2 = m_siInfo.GetInt32("HashSize");
      m_comparer = (IEqualityComparer<TKey>)
      m_siInfo.GetValue("Comparer", typeof(IEqualityComparer<TKey>));
      ...// reconstruct the Dictionary's internal states such as buckets, entries etc

   }
   ...
}

I have a question: Why does Dictionaryneeds need to implement IDeserializationCallback rather than just do every thing in the specail constructor?

as canton7 said:

The dictionary's contents are serialized when the dictionary is serialized -- every key and value stored in the dictionary is serialized. The dictionary cannot calculate the hash codes for any of its contents until they've finished being deserialized. Therefore the calculation of the hash codes is deferred until OnDeserialization, by which point each of the keys and values in the dictionary have finished being deserialized, and it is safe to call methods on them

But below is the quote from the book CLR via C#:

When a formatter serializes an object graph, it looks at each object. If its type implements the ISerializable interface, then the formatter ignores all custom attributes and instead constructs a new System.Runtime.Serialization.SerializationInfo object. This object contains the actual set of values that should be serialized for the object.

We can see that it is the SerializationInfo object will be serialized, the Dictionary object itself won't be serilailized.

As the formatter extracts SerializationInfo object from the stream, it creates a new Dictionary object (by calling the FormatterServices.GetUninitializedObject method). Initially, all of this Dictionary object’s fields are set to 0 or null.

And we already have SerializationInfo object back and passed it to the special constructor, you have all original keys/values before serialization, then you can reconstruct the Dictionary's internal states in the specail constructor just as

protected Dictionary(SerializationInfo info, StreamingContext context) {    
   if (info == null) return; // Never set, return
   Int32 num = info.GetInt32("Version");
   Int32 num2 = info.GetInt32("HashSize");
   m_comparer = (IEqualityComparer<TKey>)
   info.GetValue("Comparer", typeof(IEqualityComparer<TKey>));
   ...// reconstruct the Dictionary's internal states such as buckets, entries etc
}

Is my understanding correct?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
156 views
Welcome To Ask or Share your Answers For Others

1 Answer

I'm not sure where you're getting your code from, but if you look at the actual source:

protected Dictionary(SerializationInfo info, StreamingContext context)
{
    // We can't do anything with the keys and values until the entire graph has been deserialized
    // and we have a resonable estimate that GetHashCode is not going to fail.  For the time being,
    // we'll just cache this.  The graph is not valid until OnDeserialization has been called.
    HashHelpers.SerializationInfoTable.Add(this, info);
}

From the comment, the problem is clear: the serialized data will contain the dictionary's entries, yes, but also serialized versions of the keys for those entries. The dictionary needs to re-calculate the hash code of each entry (answered in another of your many questions on this), and it can't do this until each entry has been properly deserialized. Since the order of deserialization is not guaranteed (answered in another of your questions), the dictionary needs to wait until the entire graph has been reconstructed before calculating any of those hash codes.


Let's take a simple example:

public class Program
{
    public static void Main()
    {
        var container = new Container() { Name = "Test" };
        container.Dict.Add(container, "Container");
        
        var formatter = new BinaryFormatter();
        var stream = new MemoryStream();
        formatter.Serialize(stream, container);
        
        stream.Position = 0;
        formatter.Deserialize(stream);
    }
}

[Serializable]
public class Container : ISerializable
{
    public string Name { get; set; }
    public MyDictionary Dict { get; }
    
    public Container()
    {
        Dict = new MyDictionary();
    }
    
    protected Container(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("Container deserialized");

        Name = info.GetString("Name");
        Dict = (MyDictionary)info.GetValue("Dict", typeof(MyDictionary));
    }
    
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Name", Name);
        info.AddValue("Dict", Dict);
    }
    
    public override bool Equals(object other) => (other as Container)?.Name == Name;
    public override int GetHashCode() => Name.GetHashCode();
}

[Serializable]
public class MyDictionary : Dictionary<object, object>
{
    public MyDictionary() { }

    protected MyDictionary(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        Console.WriteLine("MyDictionary deserialized");
        
        // Look at the data which Dictionary wrote...
        var kvps = (KeyValuePair<object, object>[])info.GetValue("KeyValuePairs", typeof(KeyValuePair<object, object>[]));
        Console.WriteLine("Name is: " + ((Container)kvps[0].Key).Name);
    }
}

Run it here: DotNetFiddle.

(I subclassed Dictionary so we know when its deserialization constructor is called).

This prints:

MyDictionary deserialized
Name is:
Container deserialized

Notice how MyDictionary is deserialized first, before Container is deserialized, despite the fact that MyDictionary contains Container as one of its keys! You can also see that Container.Name is null when MyDictionary is deserialized -- that only gets assigned to later on, some time before OnDeserialization is called.

Since Container.Name is null when the MyDictionary is constructed, and Container.GetHashCode depends on Container.Name, then obviously incorrect things would happen if Dictionary tried to call GetHashCode on any of its items' keys at that point.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...