以前,序列化/反序列化方法使用类型
Item
:
public class Item{}
现在我有一个名为
ItemWrapper
的新类,派生自 Item
,并具有附加属性:
public class ItemWrapper : Item
{
public string NewProperty { get; set; }
}
现在我的序列化/反序列化方法使用类型
ItemWrapper
。现在我已经破坏了向后兼容性。我无法加载在旧版本中保存的任何类型为 Item
的 XML 文件。我考虑过在反序列化方法尝试将 Item
反序列化为 ItemWrapper
时在反序列化方法上放置一个 try/catch,然后在 catch 中我会尝试将其反序列化为 Item
。或者我可以通过 xPath 查看 XML 结构,如果没有找到 ItemWrapper
,我可以将其假设为 Item
。这两种解决方案都感觉很笨拙,我确信有更好的方法来处理这种情况。有什么想法吗?
问得好。首先,虽然通过派生扩展类很好地遵守了开放/关闭原则,但如果 Item 的所有使用者现在都使用 ItemWrapper,那么您在实现派生类时就没有节省太多精力。如果 ItemWrapper 现在是唯一正在使用的具体对象,则将其新属性与 Item 合并即可完成。
如果您需要保留这两个具体内容,那么 ItemWrapper 需要用一些属性进行修饰,以指示 XMLSerializer 应如何与 XML 字符串相互转换。有关 XML 属性的信息可以在此处找到。
我特别提请您注意XmlTypeAttribute。这会装饰 ItemWrapper 类本身,并告诉 XmlSerializer 使用特定的命名空间和类型名称,而不是根据类名称自动生成它们。您可以使用它来使 ItemWrapper 与序列化 Item 创建的 XML 文件兼容,方法是声明 ItemWrapper 类应创建和使用标记为
<Item>
的 XML 序列化。但是,如果 Item 仍然存在,则在尝试反序列化通过序列化 ItemWrapper 创建的文件时将会失败,因此此解决方案不向前兼容,因此如果您尚未处理序列化错误,则您的软件的早期版本不兼容稳健地,当给定新文件时,会无缘无故地死掉
出于这个原因,在序列化中实现某种版本控制方案通常是一个好主意。它可以像类型上的公共只读属性一样简单,可以使用 XmlAttribute 属性进行标记,告诉 XmlSerializer 将 <Item>
标记构造为
<Item xmlVersion="1.0.0">
。如果您之前已完成此操作,则 ItemWrapper 可以覆盖该字段以返回“1.1.0”,从而允许 XML 文件轻松区分,从而允许您使用 XmlTextReader 检查不兼容的文件版本,并优雅地返回错误如果该文件是由更高版本的软件生成的。保留
Item
成员,但将其更改为在序列化/反序列化期间执行您想要的操作。这假设您的代码不再使用它;它仅用于序列化:
// "Item MyItem" is what you had in old version.
// This member is not used in new code; exists only for serialization.
public Item MyItem
{
get => null; // Serializes to null; won't appear in the XML output.
set
{
// When deserialize old version, this does what is needed.
if (value != null)
_myNewItem = MakeItemWrapperFromItem(value);
}
}
// The new member.
public ItemWrapper MyNewItem
{
get => _myNewItem;
set => _myNewItem = value;
}
private ItemWrapper _myNewItem;
private ItemWrapper MakeItemWrapperFromItem(Item value)
{
// Your code here.
}
子类
XMLSerializer
,并在那里控制序列化/反序列化。
如何:控制派生类的序列化我喜欢这个版本的想法,但不幸的是“Item”并没有这样做。但是有更高版本号吗?通常,版本控制仅在将成为 XML 文件“根”的类上完成。
这使得 XMLSerializer 的子类可以轻松处理不同的版本。