Десериализация производных классов с помощью пользовательского SerializationBinder

Я пытаюсь десериализовать некоторые объекты, которые были сериализованы в старой версии моего приложения, чтобы обновить их до нового формата, который я использую в новой версии приложения.

Для этого я использую пользовательский SerializationBinder для сопоставления старых объектов с новыми.

Таким образом я могу перенести большинство своих объектов, но у меня возникает проблема, когда один из моих объектов является производным от базового класса. Проблема в том, что свойства внутри базового класса не будут десериализованы (будут десериализованы только свойства производного класса).

Мне удалось сузить проблему до короткой автономной программы, которую я вставлю сюда:

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; }
}
}

В этой программе я сериализую объект, производный от v1, и пытаюсь десериализовать его как объект, производный от v2. Оба объекта совершенно одинаковы, но программа не десериализует свойства a и b.

Вот что я получаю: a=0 b=0 c=3 d=4

Я предполагаю, что проблема связана с автосвойствами. Если убрать {get;set;} и превратить их в поля, то работать будет. Но объекты версии 1 в моем приложении являются свойствами, поэтому мне приходится с ними работать.

Итак, вопрос: как я могу заставить эту десериализацию работать правильно?


person Ove    schedule 21.10.2014    source источник


Ответы (1)


Вы должны предоставить конструктор десериализации и реализовать ISerializable для новых типов версий. К членам старой версии можно получить доступ из SerializationInfo, используя вспомогательный класс 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);
    }
}
person Andrew Karpov    schedule 21.10.2014
comment
Кажется, это работает, но есть ли способ сделать это без изменения классов v1base и v1derived? Мне нужно сделать это в более крупном приложении, куда я хочу перенести некоторые старые объекты, и я не могу изменить их определение. - person Ove; 21.10.2014
comment
Я еще немного покопался, и похоже, что вы можете получить значения, не изменяя v1base и v1derived. Вам нужно изменить вызов внутри конструктора сериализации v2base и v2derived на что-то вроде этого, и он будет работать: a = info.GetInt32("v1base+<a>k__BackingField"); (то же самое для b, c и d) - person Ove; 21.10.2014
comment
Да, я только что сделал то же самое :) Интересно, я никогда не делал этого раньше - person Andrew Karpov; 21.10.2014
comment
И я нет. Сегодня я узнал о двоичной сериализации гораздо больше, чем хотел. Хорошо, что я перехожу на сериализацию xml. Если вы добавите это в свой ответ, я отмечу его как принятый. - person Ove; 21.10.2014