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


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

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

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

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

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

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

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

Как я могу это сделать??

Пьерлуиджи

5 6

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-в face, как ни странно, он даже не реализует старый интерфейс IEnumerable.

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

public MainClass(SerializationInfo info, StreamingContext context) {}

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

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

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

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.");
    }
}

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

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

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

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

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

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

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

 Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)