Xerillio
Xerillio

Reputation: 5259

Deserializing .NET Dictionary using ISerializable

I have problems getting de-/serialization of a Dictionary working when implementing ISerializable in the enclosing class. It seems to be able to automatically de-/serialize if I just apply the SerializableAttribute. I need to check the deserialized Dictionary in the process however, so I need ISerializable to be working.

I set up a little test to be sure it wasn't due to some other problems. The Test class looks like this:

[Serializable]
class Test : ISerializable
{
    private Dictionary<string, int> _dict;

    public Test()
    {
        var r = new Random();
        _dict = new Dictionary<string, int>()
        {
            { "one", r.Next(10) },
            { "two", r.Next(10) },
            { "thr", r.Next(10) },
            { "fou", r.Next(10) },
            { "fiv", r.Next(10) }
        };
    }

    protected Test(SerializationInfo info, StreamingContext context)
    {
        // Here _dict.Count == 0
        // So it found a Dictionary but no content?
        _dict = (Dictionary<string, int>)info.GetValue("foo", typeof(Dictionary<string, int>));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("foo", _dict, typeof(Dictionary<string, int>));
    }

    public override string ToString()
    {
        var sb = new StringBuilder();
        foreach (var pair in _dict)
            sb.Append(pair.Key).Append(" : ").Append(pair.Value).AppendLine();
        return sb.ToString();
    }
}

And the main to test it:

static void Main(string[] args)
{
    var t1 = new Test();
    Console.WriteLine(t1);
    var formatter = new BinaryFormatter();
    using (var stream = new FileStream("test.test", FileMode.Create, FileAccess.Write, FileShare.None))
        formatter.Serialize(stream, t1);

    Test t2;
    using (var stream = new FileStream("test.test", FileMode.Open, FileAccess.Read, FileShare.Read))
        t2 = (Test)formatter.Deserialize(stream);
    Console.WriteLine(t2);
    Console.ReadLine();
}

The output in the console is the same before and after. But as commented in the Test class, the overloaded constructor doesn't read any content in the deserialized Dictionary.

Am I doing something wrong or is this a bug/subtle side effect?

Upvotes: 3

Views: 1554

Answers (1)

alexm
alexm

Reputation: 6882

Dictionary<TKey, TValue> implements IDeserializationCallback and defers the completion of its de-serialization until the whole graph of objects is read back. You can see how it was actually implemented on Reference 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);
}

To force the completion call _dict.OnDeserialization() in your code:

protected Test(SerializationInfo info, StreamingContext context)
{
    // Here _dict.Count == 0
    // So it found a Dictionary but no content?
    _dict = (Dictionary<string, int>)info.GetValue("foo", typeof(Dictionary<string, int>));

    _dict.OnDeserialization(null);

    // Content is restored.
    Console.WriteLine("_dict.Count={0}", _dict.Count);
}

PS: HashSet<T>, SortedSet<T>, LinkedList<T> and maybe few other container types exhibit the same behavior

Upvotes: 3

Related Questions