.NET与BinaryFormatter的向后兼容性 [英] Backwards compatibility in .NET with BinaryFormatter

查看:106
本文介绍了.NET与BinaryFormatter的向后兼容性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在C#游戏中使用BinaryFormatter,以保存用户游戏进度,游戏级别等。我们遇到了向后兼容的问题。

We use BinaryFormatter in a C# game, to save user game progress, game levels, etc. We are running into the problem of backwards compatibility.

目标是:


  • 关卡设计师创建广告系列(关卡和规则),我们更改了代码,广告系列仍然可以正常运行。

  • 用户保存游戏,我们发布了一个游戏补丁,用户仍应能够加载游戏

  • 不管两个版本有多远,不可见数据转换过程都应该起作用。例如,用户可以跳过我们的前5个次要更新,而直接获得第6个。尽管如此,他保存的游戏仍然可以正常加载。

该解决方案对于用户和关卡设计师必须是完全不可见的,并且负担最少的编码人员谁想要更改某些内容(例如,因为想到了更好的名称而重命名字段)。

The solution needs to be completely invisible to users and level designers, and minimally burden coders who want to change something (e.g. rename a field because they thought of a better name).

我们序列化的某些对象图植于一个类中,而某些植根于其他类中。不需要向前兼容。

Some object graphs we serialize are rooted in one class, some in others. Forward compatibility is not needed.

可能会破坏更改(以及序列化旧版本并反序列化为新版本时会发生的情况):

Potentially breaking changes (and what happens when we serialize the old version and deserialize into the new):


  • 添加字段(默认初始化)

  • 更改字段类型(失败)

  • 重命名字段(等效于删除它并添加一个新字段)

  • 将属性更改为字段并返回(等效于重命名)

  • 将自动实现的属性更改为使用后备字段(相当于重命名)

  • 添加超类(相当于将其字段添加到当前类中)

  • 以不同的方式解释字段(例如

  • 对于实现ISerializable的类型,我们可能会更改ISerializable方法的实现(例如,对于某些非常大的类型,开始在ISerializable实现中使用压缩)
  • >
  • 重命名一个类,重命名一个枚举值

  • add field (gets default-initialized)
  • change field type (failure)
  • rename field (equivalent to removing it and adding a new one)
  • change property to field and back (equivalent to a rename)
  • change autoimplemented property to use backing field (equivalent to a rename)
  • add superclass (equivalent to adding its fields to the current class)
  • interpret a field differently (e.g. was in degrees, now in radians)
  • for types implementing ISerializable we may change our implementation of the ISerializable methods (e.g. start using compression within the ISerializable implementation for some really large type)
  • Rename a class, rename an enum value

我已阅读过以下内容:

  • Version Tolerant Serialization
  • IDeserializationCallback
  • [OptionalField(VersionAdded)]
  • [OnDeserializing], [OnDeserialized], [OnSerializing], [OnSerialized].
  • [NotSerialized]

我当前的解决方案


  • 我们通过使用OnDeserializing回调之类的东西来进行尽可能多的不间断更改。

  • 我们计划每两周进行一次重大更改,因此无需保留任何兼容性代码。

  • 每次在进行重大更改之前,我们都会复制 all [Serializable]我们使用的类,放入名为OldClassVersions.VersionX的名称空间/文件夹中(其中X是最后一个之后的下一个序数)。即使不打算很快发布,我们也会这样做。

  • 写入文件时,我们序列化的是该类的实例:类SaveFileData {int version;对象数据; }

  • 从文件读取时,我们反序列化SaveFileData并将其传递给迭代的 update例程,该例程执行以下操作:

  • We make as many changes as possible non-breaking, by using stuff like the OnDeserializing callback.
  • We schedule breaking changes for once every 2 weeks, so there's less compatibility code to keep around.
  • Everytime before we make a breaking change, we copy all the [Serializable] classes we use, into a namespace/folder called OldClassVersions.VersionX (where X is the next ordinal number after the last one). We do this even if we aren't going to be making a release soon.
  • When writing to file, what we serialize is an instance of this class: class SaveFileData { int version; object data; }
  • When reading from file, we deserialize the SaveFileData and pass it to an iterative "update" routine that does something like this:

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类是序列化的东西。

    • 几乎不可能测试或调试

    • 需要保留很多类的旧副本,这容易出错,脆弱且令人生厌

    • 我不知道要重命名课程时该怎么做

    • the deserializer deserializes to class Foo rather than to class OldClassVersions.Version5.Foo - because class Foo is what was serialized.
    • almost impossible to test or debug
    • requires to keep around old copies of a lot of classes, which is error-prone, fragile and annoying
    • I don't know what to do when we want to rename a class

    这应该是一个非常普遍的问题。人们通常是如何解决它的?

    This should be a really common problem. How do people usually solve it?

    推荐答案

    困难的一个。我将转储二进制文件并使用XML序列化(更易于管理,可以容忍不太极端的更改-例如添加/删除字段)。在更极端的情况下,编写从一个版本到另一个版本的转换(也许是xslt)并保持类的清洁比较容易。如果需要不透明度和较小的磁盘空间,则可以尝试在写入磁盘之前先压缩数据。

    Tough one. I would dump binary and use XML serialization (easier to manage, tolerant to changes that are not too extreme - like adding / removing fields). In more extreme cases it is easier to write a transform (xslt perhaps) from one version to another and keep the classes clean. If opacity and small disk footprint are a requirement you can try to compress the data before writing to disk.

    这篇关于.NET与BinaryFormatter的向后兼容性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆