2010-08-27 2 views
8

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

Цели:

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

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

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

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

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

Я прочитал о:

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

  • Мы делаем так много изменений, как это возможно неразрывный, используя материал как OnDeserializing обратного вызова.
  • Мы планируем прерывать изменения один раз каждые 2 недели, поэтому есть меньше кода совместимости, чтобы поддерживать его.
  • Каждый раз, когда мы вносим изменения, мы копируем все классы [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 является то, что было сериализовать.
  • почти невозможно проверить или отладить
  • требует, чтобы держать вокруг старых копий много классов, которая подвержена ошибкам, хрупкая и раздражает
  • Я не знаю, что делать, если мы хотим, чтобы переименовать класс

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

+0

Вы решили переключиться на сериализацию xml или нашли ли вы лучший способ сделать это? Сериализация XML имеет ограничения, которые не будут работать для моей программы, поэтому я планирую следовать вашему методу с некоторыми дополнениями К.Хоффманна. – i8abug

+0

@ i8abug: Я переключился на сериализацию XML, да. Если вы расскажете мне об ограничениях, которые вас беспокоят, я могу рассказать вам обойти их, если я их знаю. –

+0

Спасибо! Для начала мне нужно сериализовать частные и защищенные члены и общие словари. Я попытался взглянуть на сериализацию DataContract, но мне нужно сериализовать неизвестные унаследованные классы (написанные другими разработчиками), и это невозможно в DataContracts. Единственное, что работает, - это двоичная сериализация. Я также проверил protobuf-net, но столкнулся с некоторыми ограничениями. Когда вы переключились на сериализацию XML, как вы справились с верификацией? Вы создали определенный интерпретатор для каждой версии вашего XML или вы все еще делаете что-то вроде описанного выше? – i8abug

ответ

3

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

+0

BinarySerialization * * допускает толерантность к версии для небольших изменений, включая добавление/удаление полей. Что именно вы подразумеваете под «легче управлять»? На самом деле XSLT звучит как отличное решение. И нет, размер файла и производительность не являются проблемой. –

+0

«проще управлять». Я имею в виду понятный для человека характер формата XML и развязку представления данных из структуры классов (вы можете контролировать, как сериализованный XML может выглядеть через атрибуты, а структура не необходимо отразить фактический класс) –

2

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

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

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

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

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

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

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

Смежные вопросы