版本容错序列化

在 .NET Framework 1.0 和 1.1 版中,创建可以在应用程序的一个版本和下一个版本中重复使用的可序列化类型时,会出现问题。如果通过添加额外的字段来修改类型,可能会发生下列问题:

  • 在要求反序列化旧类型应用程序的新版本时,该应用程序的较旧版本可能会引发异常。

  • 在反序列化缺少数据的某个类型应用程序的较旧版本时,该应用程序的较新版本可能会引发异常。

版本容错序列化 (VTS) 是 .NET Framework 2.0 中引入的一组功能,使得更加容易随时间的推移来修改可序列化类型。尤其是,为应用了 SerializableAttribute 属性的类启用了 VTS 功能。通过 VTS 可以向这些类中添加新字段,又不会破坏与该类型的其他版本的兼容性。有关工作示例应用程序,请参见版本容错序列化技术示例

VTS 功能在使用 BinaryFormatter 时启用。此外,除新增的数据容错以外的所有功能在使用 SoapFormatter 时也会启用。有关使用这些类进行序列化的更多信息,请参见二进制序列化

备注

在未来的某个时间,BinaryFormatter 的新增数据容错功能也许会作为 .NET Framework 1.1 的修补程序提供。

功能列表

功能集中包括下列功能:

  • 无关或意外数据的容错。这样使该类型的较新版本可以向较旧版本发送数据。

  • 缺少可选数据的容错。这样使较旧版本可以向较新版本发送数据。

  • 序列化回调。这样,可以在缺少数据时启用智能默认值设置。

此外,在添加了新的可选字段后,还要声明一项功能。此功能是 OptionalFieldAttribute 特性的 VersionAdded 属性。

下面对这些功能进行了更详细地讨论。

无关或意外数据的容错

过去,在反序列化期间,任何无关或意外数据都会引发异常。使用 VTS,在相同情况下,任何无关或意外数据都将被忽略,而不是引发异常。这样,使用类型的较新版本(即包括更多字段的版本)的应用程序可以向期望使用相同类型的较旧版本的应用程序发送信息。

在以下示例中,在较旧的应用程序反序列化较新的版本时,Address 类 2.0 版的 CountryField 中包含的额外数据将被忽略。

// Version 1 of the Address class.
[Serializable]
public class Address
{
    public string Street;
    public string City;
}
// Version 2.0 of the Address class.
[Serializable]
public class Address
{
    public string Street;
    public string City;
    // The older application ignores this data.
    public string CountryField;
}
' Version 1 of the Address class.
<Serializable> _
Public Class Address
    Public Street As String
    Public City As String
End Class

' Version 2.0 of the Address class.
<Serializable> _
Public Class Address
    Public Street As String
    Public City As String
    ' The older application ignores this data.
    Public CountryField As String
End Class

缺少数据的容错

通过应用 OptionalFieldAttribute 属性可以将字段标记为可选。在反序列化期间,如果缺少可选数据,序列化引擎将忽略数据的不存在,不会引发异常。因此,期望使用类型的较旧版本的应用程序可以向期望使用相同类型的较新版本的应用程序发送数据。

以下示例显示 Address 类的 2.0 版,其中 CountryField 字段标记为可选。如果较旧的应用程序向期望使用 2.0 版数据的较新应用程序发送版本 1 数据,将忽略数据的不存在。

[Serializable]
public class Address
{
    public string Street;
    public string City;

    [OptionalField]
    public string CountryField;
}
<Serializable> _
Public Class Address
    Public Street As String
    Public City As String

    <OptionalField> _
    Public CountryField As String
End Class

序列化回调

序列化回调是在四个点提供序列化/反序列化进程的挂钩的机制。

属性 调用关联的方法时 典型用途

OnDeserializingAttribute

在反序列化之前。*

初始化可选字段的默认值。

OnDeserializedAttribute

在反序列化之后。

根据其他字段的内容确定可选字段的值。

OnSerializingAttribute

在序列化之前。

准备序列化。例如,创建可选的数据结构。

OnSerializedAttribute

在序列化之后。

记录序列化事件。

* 此回调在反序列化构造函数(如果存在)之前调用。

使用回调

要使用回调,请将相应的属性应用于接受 StreamingContext 参数的方法。每个类只有一个方法可以使用其中每个属性进行标记。例如:

[OnDeserializing]
private void SetCountryRegionDefault(StreamingContext sc)
{
    CountryField = "Japan";
}
[OnDeserializing]
private void SetCountryRegionDefault(StreamingContext sc)
{
    CountryField = "Japan";
}

这些方法旨在进行版本控制。在反序列化期间,如果缺少可选字段的数据,该字段可能无法正确初始化。要纠正此问题,可创建赋予正确值的方法,然后将 OnDeserializingAttribute 或 OnDeserializedAttribute 属性应用于该方法。

以下示例在类型上下文中显示该方法。如果应用程序的较早版本向其较新版本发送 Address 类的实例,将缺少 CountryField 字段数据。但在反序列化之后,该字段将设置为默认值“Japan”。

[Serializable]
public class Address
{
    public string Street;
    public string City;
    [OptionalField]
    public string CountryField;

    [OnDeserializing]
    private void SetCountryRegionDefault (StreamingContext sc)
    {
        CountryField = "Japan";
    }
}
<Serializable> _
Public Class Address
    Public Street As String
    Public City As String
    <OptionalField> _
    Public CountryField As String

    <OnDeserializing> _
    Private Sub SetCountryRegionDefault(StreamingContext sc)
        CountryField = "Japan";
    End Sub
End Class

VersionAdded 属性

OptionalFieldAttribute 具有 VersionAdded 属性。在 .NET Framework 2.0 版中不使用此属性。但是,正确设置此属性是非常重要的,这样可以确保类型与未来的序列化引擎兼容。

该属性指示已添加给定字段的类型的版本。每次修改该类型时,该属性应递增 1(从 2 开始),如下例所示:

// Version 1.0
[Serializable]
public class Person
{
    public string FullName;
}

// Version 2.0
[Serializable]
public class Person
{
    public string FullName;

    [OptionalField(VersionAdded = 2)]
    public string NickName;
    [OptionalField(VersionAdded = 2)]
    public DateTime BirthDate;
}

// Version 3.0
[Serializable]
public class Person
{
    public string FullName;

    [OptionalField(VersionAdded=2)]
    public string NickName;
    [OptionalField(VersionAdded=2)]
    public DateTime BirthDate;

    [OptionalField(VersionAdded=3)]
    public int Weight;
}
' Version 1.0
<Serializable> _
Public Class Person
    Public FullName
End Class

' Version 2.0
<Serializable> _
Public Class Person
    Public FullName As String

    <OptionalField(VersionAdded := 2)> _
    Public NickName As String
    <OptionalField(VersionAdded := 2)> _
    Public BirthDate As DateTime
End Class

' Version 3.0
<Serializable> _
Public Class Person
    Public FullName As String

    <OptionalField(VersionAdded := 2)> _
    Public NickName As String
    <OptionalField(VersionAdded := 2)> _
    Public BirthDate As DateTime

    <OptionalField(VersionAdded := 3)> _
    Public Weight As Integer
End Class

最佳做法

为了确保正确的版本控制行为,修改类型版本时应遵守下列规则:

  • 一定不要移除已序列化的字段。

  • 如果 NonSerializedAttribute 属性在上一个版本中未应用于某个字段,一定不要对该字段应用此属性。

  • 一定不要更改已序列化字段的名称或类型。

  • 在添加新的已序列化字段时,应用 OptionalFieldAttribute 属性。

  • 在从(上一个版本中不可序列化的)字段中移除 NonSerializedAttribute 属性时,应用 OptionalFieldAttribute 属性。

  • 对于所有可选字段,使用序列化回调设置有意义的默认值,除非可以接受 0 或 null 作为默认值。

为了确保类型将与未来的序列化引擎兼容,请遵循下列准则:

  • 始终正确设置 OptionalFieldAttribute 特性的 VersionAdded 属性。

  • 避免分支的版本控制。

请参见

参考

SerializableAttribute
BinaryFormatter
SoapFormatter
VersionAdded
OptionalFieldAttribute
OnDeserializingAttribute
OnDeserializedAttribute
OnDeserializingAttribute
OnSerializedAttribute
StreamingContext
NonSerializedAttribute

其他资源

二进制序列化