1

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?

Ove
  • 6,227
  • 2
  • 39
  • 68

1 Answers1

1

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);
    }
}
Andrew Karpov
  • 431
  • 2
  • 7
  • This seems to work, but is there any way to do it without modifying the `v1base` and `v1derived` classes? I need to do this in a larger application where I want to migrate some old objects, and I can't change their definition. – Ove Oct 21 '14 at 09:52
  • I did some more digging and it looks like you can get the values without modifying `v1base` and `v1derived`. You need to change the call inside v2base's and v2derived's serializing constructor to something like this and it will work: `a = info.GetInt32("v1base+k__BackingField");` (the same for b, c and d) – Ove Oct 21 '14 at 10:01
  • Yes, I've done the same just now :) Interesting, I've never done this before – Andrew Karpov Oct 21 '14 at 10:09
  • Me neither. I've learned much more than I wanted to about binary serialization today. Good thing I'm migrating over to xml serialization. If you add this thing to your answer, I will mark it as accepted. – Ove Oct 21 '14 at 10:11