Обратная совместимость в .NET с BinaryFormatter - PullRequest
9 голосов
/ 27 августа 2010

Мы используем BinaryFormatter в игре C #, чтобы сохранить прогресс пользовательской игры, игровые уровни и т. Д. Мы сталкиваемся с проблемой обратной совместимости.

Цели:

  • Дизайнер уровней создает кампанию (уровни и правила), мы меняем код, кампания все равно должна работать нормально.Это может происходить каждый день во время разработки перед выпуском.
  • Пользователь сохраняет игру, мы выпускаем игровой патч, пользователь все равно должен иметь возможность загрузить игру
  • Невидимый процесс преобразования данных должен работать независимо от того, какдалекие две версии.Например, пользователь может пропустить наши первые 5 небольших обновлений и получить 6-е непосредственно.Тем не менее, его сохраненные игры по-прежнему должны нормально загружаться.

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

Некоторые графы объектов, которые мы сериализуем, коренятся в одном классе, некоторые в других.Прямая совместимость не требуется.

Потенциально прерывание изменений (и что происходит, когда мы сериализуем старую версию и десериализуем в новую):

  • add field (получает инициализацию по умолчанию)
  • изменить тип поля (ошибка)
  • переименовать поле (эквивалентно его удалению и добавлению нового)
  • изменить свойство на поле и обратно (эквивалентно переименованию)
  • изменить свойство автоопределения для использования вспомогательного поля (эквивалентно переименованию)
  • добавить суперкласс (эквивалентно добавлению его полей в текущий класс)
  • интерпретировать поле по-другому (например, было вградусы, теперь в радианах)
  • для типов, реализующих ISerializable, мы можем изменить нашу реализацию методов ISerializable (например, начать использовать сжатие в реализации ISerializable для некоторого действительно большого типа)
  • Переименовать класс,переименуйте значение enum

Я читал о:

Мое текущее решение :

  • Мы вносим как можно больше изменений, не нарушая,используя такие вещи, как обратный вызов OnDeserializing.
  • Мы планируем критические изменения раз в две недели, поэтому кода совместимости остается меньше.
  • Каждый раз перед внесением критических изменений мы копируем все классы [Serializable], которые мы используем, в пространство имен / папку с именем OldClassVersions.VersionX (где X - следующий порядковый номер после последнего).Мы делаем это, даже если не собираемся выпускать релиз в ближайшее время.
  • При записи в файл мы сериализуем экземпляр этого класса: class SaveFileData {int version;данные объекта;}
  • При чтении из файла мы десериализуем SaveFileData и передаем его в итеративную процедуру «обновления», которая выполняет что-то вроде этого:

.

for(int i = loadedData.version; i < CurrentVersion; i++)
{
    // Update() takes an instance of OldVersions.VersionX.TheClass
    // and returns an instance of OldVersions.VersionXPlus1.TheClass
    loadedData.data = Update(loadedData.data, i);
}
  • Для удобства функция Update () в своей реализации может использовать функцию CopyOverlappingPart (), которая использует отражение для копирования как можно большего количества данных из старой версии в новую версию.Таким образом, функция Update () может обрабатывать только те вещи, которые действительно изменились.

Некоторые проблемы с этим:

  • десериализатор десериализуется в класс Foo, а не в класс OldClassVersions.Version5.Foo - потому что класс Foo - это то, что было сериализовано.
  • почти невозможно протестировать или отладить
  • требует хранения старых копий множества классов, которые подвержены ошибкам и хрупкии раздражает
  • Я не знаю, что делать, когда мы хотим переименовать класс

Это должно быть действительно распространенной проблемой.Как люди обычно решают это?

Ответы [ 2 ]

3 голосов
/ 27 августа 2010

Жесткий.Я бы сделал дамп двоичного файла и использовал бы сериализацию XML (проще в управлении, терпим к изменениям, которые не слишком экстремальны - например, добавление / удаление полей).В более экстремальных случаях легче написать преобразование (возможно, xslt) из одной версии в другую и сохранить классы чистыми.Если непрозрачность и небольшой размер дискового пространства необходимы, вы можете попытаться сжать данные перед записью на диск.

2 голосов
/ 27 августа 2010

В нашем приложении возникла та же проблема с хранением данных профиля пользователя (расположение столбцов сетки, настройки фильтра ...).

В нашем случае проблема была в AssemblyVersion.

Для этой проблемы я создаю SerializationBinder, который читает актуальную версию сборки сборки (все сборки получают новый номер версии при новом развертывании) с Assembly.GetExecutingAssembly().GetName().Version.

В переопределенном методе BindToType информация о типе создается с новой версией сборки.

Десериализация осуществляется «вручную», что означает

  • Десериализация с помощью обычного двоичного формата
  • получить все поля, которые должны быть десериализованы (аннотированы собственным атрибутом)
  • заполнить объект данными из десериализованного объекта

Работает со всеми нашими данными и с трех или четырех выпусков.

...