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

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

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

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

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

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

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

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

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

Пьерлуиджи

Ответ 1

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.

Ответ 2

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

public MainClass(SerializationInfo info, StreamingContext context) {}

вы можете использовать info-объект, переданный конструктору для извлечения данных из сериализованного файла. по умолчанию (т.е. когда 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.");
    }
}

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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