Search code examples
c#deserializationhashtablebinaryformatter

'OnDeserialization method was called while the object was not being deserialized.' from hashtable deserialization


I have a hash table that stores keys/values necessary for my application to work after power-losses. They are stored to file using BinaryFormatter. I have attempted to account for versioning by only putting default value types into the hash table, such as int, float, string, object etc. but have added some enums as well.

Sometimes when updating the application, the hash table is not able to deserialize properly. I suspect either the obfuscator I use (all enums are prevented from symbol renaming) or a field somehow having been added to an object that I have missed. The deserialization call:

var hashtable = new Hashtable();

                using (var stream = new FileStream(fileName, FileMode.Open))
                {
                    var formatter = new BinaryFormatter(selector, context);

                    try
                    {
                        hashtable = (Hashtable) formatter.Deserialize(stream);

To remedy this, I have made a serializationsurrogate class as shown below. When deserializing, I get the following SerializationException:'OnDeserialization method was called while the object was not being deserialized.'

[Serializable]
public class HashtableSerializationSurrogate : IDeserializationCallback, ISerializationSurrogate
{
    public HashtableSerializationSurrogate() : base()
    {
    }   

    private static SerializationInfo serializationInfo = null;

    void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        Hashtable hashtable = (Hashtable) obj;

        object[] objArray1 = new object[hashtable.Count];
        object[] objArray2 = new object[hashtable.Count];

        hashtable.Keys.CopyTo(objArray1, 0);
        hashtable.Values.CopyTo(objArray2, 0);

        // ISSUE: type reference
        info.AddValue("Version", 0, typeof(int));
        // ISSUE: type reference
        info.AddValue("Keys", objArray1, typeof(object[]));
        // ISSUE: type reference
        info.AddValue("Values", objArray2, typeof(object[]));

        // How much space to allocate to the hashtable.
        info.AddValue("HashSize", (fieldinfo.GetValue(obj) as Array).Length);

        serializationInfo = info;
    }


    private static FieldInfo fieldinfo = typeof(Hashtable).GetField("buckets", (BindingFlags) 36);

    object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        return obj;
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
        var hashtable = (Hashtable)sender;
        hashtable.OnDeserialization(this);

        // Populate hash table
        object[] keys = (object[])serializationInfo.GetValue("Keys", typeof(object[]));
        object[] values = (object[])serializationInfo.GetValue("Values", typeof(object[]));

        // Add each (key,value] pair to the actual Hashtable
        for (int x = 0; x < keys.Length; x++)
            hashtable.Add(keys[x], values[x]);

        serializationInfo = null;
    }
}

I am completely stuck here. Does anyone know why this exception is thrown after the SetObjectData call?

Edit: Stacktrace


Solution

  • I have made a bit of research on this using the source code. First thing to note is that Hashtable class already has some version control when serializing/deserializing, so probably you don't need to introduce your own and look for the cause of the issue somewhere else.

    Now coming to your actual question, the exception is being thrown from OnDeserialization method because SerializationInfoTable is empty. And it is empty because it gets filled only when calling the serialization constructor Hashtable(SerializationInfo info, StreamingContext context), which is not called when using surrogates. SerializationInfoTable is an internal property in a nested internal class, so you can't do much with it.

    What you can do is create an inherited class and override GetObjectData and OnDeserialization to do your stuff there. E.g.

            [Serializable]
        public class MyHashTable : Hashtable
        {
            private static SerializationInfo serializationInfo = null;
    
            protected MyHashTable(SerializationInfo info, StreamingContext context) : base(info, context)
            {
            }
    
            public MyHashTable() : base()
            {
    
            }
            public override void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                base.GetObjectData(info, context);
                info.AddValue("MyVersion", 0, typeof(int));
    
                serializationInfo = info;
    
            }
    
            public override void OnDeserialization(object sender)
            {
                base.OnDeserialization(sender);
                var myVersion = serializationInfo.GetValue("MyVersion", typeof(string));
                Console.WriteLine($"MyVersion: {myVersion}");
            }
        }
    }
    

    Hope that helps.