.NET与BinaryFormatter的向后兼容性 [英] Backwards compatibility in .NET with 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
我已阅读过以下内容:
- 版本耐序列化
- IDeserializationCallback
- [OptionalField(VersionAdded)]
- [OnDeserializing],[OnDeserialized],[OnSerializing],[OnSerialized]。
- [未序列化]
- 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屋!