ISerializable и обратная совместимость

Мне нужно работать со старым приложением, которое использовало binaryFormatter для сериализации данных приложения в файловый поток (скажем, в файле с именем "data.oldformat") без какой-либо оптимизации, основной класс был отмечен атрибутом

<serializable()>public MainClass
....... 
end class

и код сериализации

dim b as new binaryformatter
b.serialize(mystream,mymainclass)

В попытке оптимизировать процесс сериализации / десериализации я просто заставил класс реализовать интерфейс ISerializable и написал несколько оптимизированных процедур сериализации.

<serializable()>public MainClass
       implements ISerializable
....... 
end class

Оптимизация работает очень хорошо, но я ДОЛЖЕН найти способ восстановить данные внутри старых файлов для обратной совместимости.

Как мне это сделать??

Пьерлуиджи


person pierusch    schedule 10.04.2010    source источник


Ответы (5)


stmax дает отличный ответ, однако я бы реализовал его так, в котором используется SerializationEntry.GetEnumerator() вместо try/catch. Этот способ чище и значительно быстрее.

public MainClass(SerializationInfo info, StreamingContext context) {
    int version = 0;
    foreach (SerializationEntry s in info)
    {
        if (s.Name == "version") 
        {
            version = (int)s.Value;
            break;
        }
    }

    switch (version) {
      case 0:
        // deserialize "old format"
        break;
      case 1:
        // deserialize "new format, version 1"
        break;
      default:
        throw new NotSupportedException("version " + version + " is not supported.");
    }
}

Я бы предпочел версию LINQ с использованием .FirstOrDefault (), но SerializationInfo не реализует IEnumerable - как ни странно, он даже не реализует старый интерфейс IEnumerable.

person Jared Moore    schedule 28.09.2011

поскольку вы уже реализовали интерфейс ISerializable, вы, вероятно, уже добавили требуемый конструктор:

public MainClass(SerializationInfo info, StreamingContext context) {}

вы можете использовать информационный объект, переданный конструктору, для извлечения данных из сериализованного файла. по умолчанию (т.е. когда ISerializable не реализован) имена полей используются в качестве идентификаторов во время сериализации. поэтому, если в вашем старом классе было поле "int x", вы можете десериализовать его, используя:

this.x = info.GetInt32("x");

для более новых версий я обычно добавляю запись «версия» во время сериализации, например:

public void GetObjectData(SerializationInfo info, StreamingContext context) {
  info.AddValue("version", 1);
  info.AddValue("othervalues", ...);
}

во время десериализации вы можете проверить эту запись версии и выполнить десериализацию соответственно:

public MainClass(SerializationInfo info, StreamingContext context) {
    int version;
    try {
       version = info.GetInt32("version");
    }
    catch {
       version = 0;
    }

    switch (version) {
      case 0:
        // deserialize "old format"
        break;
      case 1:
        // deserialize "new format, version 1"
        break;
      default:
        throw new NotSupportedException("version " + version + " is not supported.");
    }
}

я не скомпилировал этот код, возможно, в нем есть опечатки.

надеюсь, это поможет.

person stmax    schedule 10.04.2010
comment
отличный ответ stmax! как вы нашли поведение по умолчанию для объекта двоичного форматирования? - person pierusch; 10.04.2010
comment
Я сделал следующее, чтобы найти имена записей, которые были сериализованы с поведением по умолчанию: foreach (запись SerializationEntry в info.GetEnumerator ()) {Trace.WriteLine (entry.Name); } - person stmax; 10.04.2010
comment
Вместо того, чтобы пытаться / поймать при получении версии, вместо этого используйте SerializationInfo.GetEnumerator () и ищите поле версии. Он чище, и если поле не найдено, то отказ от исключения исключения делает всю десериализацию (в моем тестировании) в 2 раза быстрее. SerializationInfo.Get () внутренне использует тот же линейный поиск, что и сам с помощью перечислителя, что делает их обоими O (n) по количеству полей. - person Jared Moore; 28.09.2011
comment
+1 Хороший и чистый способ сохранить обратную совместимость. Однако, хотя вы правы в том, что к значениям полей можно получить доступ, используя фактические имена полей, это не относится к свойствам auto. Затем компилятор использует формат ‹PropertyName› k__BackingField. Я добавлю эту информацию к вашему ответу, чтобы вы могли принять ее, если хотите. - person Rev; 20.02.2013

Просто попробуйте то, что вы делали до сих пор

BinaryFormatter b = new BinaryFormatter();
MainClass a = b.DeSerialize(mystream) as MainClass;

Реализация ISerializable не изменила ваш исходный класс, в основном вы только что добавили несколько методов

person Camilo Sanchez    schedule 10.04.2010
comment
Я добавил требуемый конструктор (информация о serializationInfo, контекст streamingContext), поэтому я не могу использовать b.deserialize, не зная, как основной класс сохранил свои собственные данные во время сериализации по умолчанию. - person pierusch; 10.04.2010

При сериализации ваших объектов добавьте дополнительное поле версии (это не должно добавлять слишком много накладных расходов). Затем в своем методе GetObjectData попробуйте сначала получить поле версии и в зависимости от того, существует оно или нет (путем перехвата SerializationException), десериализуйте старый или новый способ. Старый способ просто сериализует все данные, поэтому вы можете просто вызвать Get ... для всех полей.

person s1mm0t    schedule 10.04.2010
comment
это нормально для процедуры сериализации, которая использует getObjectData, но проблема возникает во время процедуры десериализации, которая использует специальный конструктор new (информация как serializationinfo, контекст как serializationcontext), старая версия mainClass не реализовала ISerializable, поэтому я не знаю, как получать данные с помощью объекта streaminginfo - person pierusch; 10.04.2010

Ваш предыдущий код должен работать. У вас есть исключение? Попробуйте использовать новый конструктор:

 Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
person Hun1Ahpu    schedule 10.04.2010
comment
У меня было ... но для этого нужно знать, как двоичный форматер сохраняет данные, когда класс только помечен как сериализуемый ... - person pierusch; 10.04.2010