Сериализация списка <IMyInterface>с использованием ISerializable - PullRequest
4 голосов
/ 13 марта 2012

Спасибо, что заглянули!

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

Вот надуманный пример:

У меня есть база клиентов с сериализованными файлами, к которым им необходим доступ.Для целей этого вопроса у них есть файл "Zoo" со списком жирафов.

[Serializable]
public class Giraffe
    : ISerializable
{
    public int Age { get; private set; }

    public Giraffe(int age)
    {
        Age = age;
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Age", Age);
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    private Giraffe(SerializationInfo info, StreamingContext context)
    {
        Age = info.GetInt32("Age");
    }
}

Теперь мы развертываем новую версию нашего программного обеспечения "Zoo", и мы собираемсядля поддержки других животных, кроме жирафов, мы должны были сделать это с самого начала, но из-за нехватки времени нам пришлось выпустить 1.0 с набором функций только для жирафов.

public interface IAnimal
{
    int Age { get; }
}

[Serializable]
public class Animal
    : IAnimal,
    ISerializable
{
    public int Age { get; private set; }

    public Animal (int age)
    {
        Age = age;
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Age", Age);
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    private Animal(SerializationInfo info, StreamingContext context)
    {
        Age = info.GetInt32("Age");
    }
}

Я использую пользовательскийserializationBinder для десериализации старых жирафов как животных

public class LegacyZooSerializationBinder
    : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        if (typeName.StartsWith("Test.Giraffe"))
            return typeof(Animal);
        else if (typeName.StartsWith("System.Collections.Generic.List`1[[Test.Giraffe"))
            return typeof(List<Animal>);
        else return null;
    }
}

Проблема в том, что я не хочу, чтобы мой класс Zoo использовал список в качестве хранилища, а не список.Я хочу сделать это по двум причинам, в том числе и для расширения в будущем, чтобы мне было легче создавать макеты для модульного тестирования.

Десериализация нового списка IAnimal не является проблемой.Проблема возникает, когда я хочу десериализовать элементы старого стиля.Binder возвращает правильный тип, вызывается правильный конструктор десериализации, все выглядит хорошо, но список на самом деле полон пустых элементов.После вызова обратного вызова IDeserializationCallback.OnDeserialization список верен.Я не могу просто вызвать IEnumerable.ConvertAll <> (), потому что похоже, что среда сериализации пытается найти точно такой же экземпляр, когда возвращается, чтобы очистить все, и ConvertAll создаст новый список.

У меня это работает на данный момент, но я надеюсь, что кто-то там может помочь мне разобраться с этим, так как это не так уж и легко поддерживать на данный момент.Вот что нужно для этого:

[Serializable]
public class Zoo
    : ISerializable,
    IDeserializationCallback
{
    List<IAnimal> m_List = null;

    List<Giraffe> m_LegacyList = null; //Just so that we can save an old-style zoo

    //Temp copy of the list
    List<Animal> m_List_Deserialization_Temp_Copy = null;

    public Zoo(bool legacy)
    {
        m_List = new List<IAnimal>();

        if (legacy)
        {
            //Create an old style zoo, just for the example
            m_LegacyList = new List<Giraffe>();
            m_LegacyList.Add(new Giraffe(0));
            m_LegacyList.Add(new Giraffe(1));
        }
        else
        {
            m_List.Add(new Animal(0));
            m_List.Add(new Animal(1));
        }
    }

    public List<IAnimal> List
    {
        get { return m_List; }
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        if(m_LegacyList != null) //Save as an old style list if we have old data
            info.AddValue("list", m_LegacyList);
        else
            info.AddValue("list", m_List);
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    private Zoo(SerializationInfo info, StreamingContext context)
    {
        try
        {
            //New style
            m_List = (List<IAnimal>)info.GetValue("list", typeof(List<IAnimal>));
        }
        catch (InvalidCastException)
        {
            //Old style
            //Put it in a temp list, until the OnDeserialization callback is called, this will be a list, full of null items!
            m_List_Deserialization_Temp_Copy = (List<Animal>)info.GetValue("list", typeof(List<Animal>));
        }
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
        if (m_List_Deserialization_Temp_Copy != null)
        {
            m_List = new List<IAnimal>();

            //This works because IEnumerable<Animal> is covariant to IEnumerable<IAnimal>
            m_List.AddRange(m_List_Deserialization_Temp_Copy);
        }
    }
}

Вот базовое тестовое консольное приложение, которое показывает сериализацию и десериализацию для обоих случаев:

    static void Main(string[] args)
    {
        {
            var item = new Zoo(false);

            var formatter = new BinaryFormatter();
            var stream = new MemoryStream();

            formatter.Serialize(stream, item);

            stream.Position = 0;

            formatter.Binder = new LegacyZooSerializationBinder();

            var deserialized = (Zoo)formatter.Deserialize(stream);

            Debug.Assert(deserialized.List.Count == 2);
            Debug.Assert(deserialized.List[0] != null);
            Debug.Assert(deserialized.List[0].Age == 0);

            Debug.Assert(deserialized.List[1] != null);
            Debug.Assert(deserialized.List[1].Age == 1);

            Console.WriteLine("New-style Zoo serialization OK.");
        }

        {
            var item = new Zoo(true);

            var formatter = new BinaryFormatter();
            var stream = new MemoryStream();

            formatter.Serialize(stream, item);

            stream.Position = 0;

            formatter.Binder = new LegacyZooSerializationBinder();

            var deserialized = (Zoo)formatter.Deserialize(stream);

            Debug.Assert(deserialized.List.Count == 2);
            Debug.Assert(deserialized.List[0] != null);
            Debug.Assert(deserialized.List[0].Age == 0);

            Debug.Assert(deserialized.List[1] != null);
            Debug.Assert(deserialized.List[1].Age == 1);

            Console.WriteLine("Old-style Zoo serialization OK.");
        }

        Console.ReadKey();
    }

Любые предложения будут приняты с благодарностью.Мне трудно найти хорошие ресурсы для такого рода вещей.Спасибо!

1 Ответ

3 голосов
/ 13 марта 2012

Попробуйте выполнить однократное преобразование старых файлов в новый формат, предпочтительно во время установки и , определенно после их резервного копирования.Таким образом, вам не придется вечно поддерживать эту странную одноразовую сериализацию, и ваша кодовая база станет существенно проще.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...