Search code examples
c#.netserializationbinary-serialization

Deserializing derived classes with custom SerializationBinder


I am trying to deserialize some objects that were serialized with an old version of my application in order to upgrade them to the new format I am using in the new version of the application.

In order to do this, I am using a custom SerializationBinder in order to map the old objects into the new ones.

I am able to migrate most of my objects this way, but I have a problem when one of my objects is derived from a base class. The problem is that the properties inside the base class won't get deserialized (only the properties in the derived class will get deserialized).

I was able to narrow down the problem into a short self-contained program that I will paste here:

namespace SerializationTest
{
class Program
{
    static void Main(string[] args)
    {
        v1derived first = new v1derived() { a = 1, b = 2, c = 3, d = 4 };
        v2derived second = null;

        BinaryFormatter bf = new BinaryFormatter();
        bf.Binder = new MyBinder();

        MemoryStream ms = new MemoryStream();
        bf.Serialize(ms, first);
        ms.Seek(0, SeekOrigin.Begin);
        second = (v2derived)bf.Deserialize(ms);
        Console.WriteLine("a={0} b={1} c={2} d={3}", second.a, second.b, second.c, second.d);
    }
}

class MyBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        if (typeName == "SerializationTest.v1base")
        {
            return typeof(v2base);
        }
        if (typeName == "SerializationTest.v1derived")
        {
            return typeof(v2derived);
        }
        return null;
    }
}

[Serializable]
class v1base
{
    public int a { get; set; }
    public int b { get; set; }
}

[Serializable]
class v1derived : v1base
{
    public int c { get; set; }
    public int d { get; set; }
}

[Serializable]
class v2base
{
    public int a { get; set; }
    public int b { get; set; }
}

[Serializable]
class v2derived : v2base
{
    public int c { get; set; }
    public int d { get; set; }
}
}

In this program I am serializing a v1derived object, and trying to deserialize it as a v2derived object. Both objects are exactly the same, but the program does not deserialize the a and b properties.

Here is the output I'm getting: a=0 b=0 c=3 d=4

I assume that the problem is related to the auto-properties. If I remove {get;set;} and turn them into fields, then it will work. But the v1 objects in my application are properties so I have to work with that.

So the question is: how can I get this deserialization to work properly?


Solution

  • You should provide deserialization constructor and implement ISerializable for new version types. Old version members can be accessed from SerializationInfo using helper class SerializationHelper:

    static class SerializationHelper
    {
        public static string GetAutoPropertyName(string baseTypeName, string name)
        {
            return baseTypeName + "+<" + name + ">k__BackingField";
        }
    
        public static string GetAutoPropertyName(string name)
        {
            return "<" + name + ">k__BackingField";
        }
    }
    
    [Serializable]
    class v2base : ISerializable
    {
        protected v2base(
            SerializationInfo info,
            StreamingContext context)
        {
            a = info.GetInt32(SerializationHelper.GetAutoPropertyName("v1base", "a"));
            b = info.GetInt32(SerializationHelper.GetAutoPropertyName("v1base", "b"));
        }
    
        public int a { get; set; }
        public int b { get; set; }
    
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(SerializationHelper.GetAutoPropertyName("v1base", "a"), a);
            info.AddValue(SerializationHelper.GetAutoPropertyName("v1base", "b"), b);
        }
    }
    
    [Serializable]
    class v2derived : v2base
    {
        protected v2derived(
            SerializationInfo info,
            StreamingContext context) : base(info, context)
        {
            c = info.GetInt32(SerializationHelper.GetAutoPropertyName("c"));
            d = info.GetInt32(SerializationHelper.GetAutoPropertyName("d"));
        }
    
        public int c { get; set; }
        public int d { get; set; }
    
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue(SerializationHelper.GetAutoPropertyName("c"), c);
            info.AddValue(SerializationHelper.GetAutoPropertyName("c"), d);
        }
    }