自定义序列化Custom serialization

自定义序列化是控制类型的序列化和反序列化的过程。Custom serialization is the process of controlling the serialization and deserialization of a type. 通过控制序列化,可以确保序列化兼容性。换而言之,在不中断类型核心功能的情况下,可在类型的不同版本之间序列化和反序列化。By controlling serialization, it's possible to ensure serialization compatibility, which is the ability to serialize and deserialize between versions of a type without breaking the core functionality of the type. 例如,在类型的第一个版本中,可能只有两个字段。For example, in the first version of a type, there may be only two fields. 在类型的下一个版本中,添加了其他几个字段。In the next version of a type, several more fields are added. 但是,第二个版本的应用程序必须可对这两种类型进行序列化和反序列化。Yet the second version of an application must be able to serialize and deserialize both types. 以下各节说明如何控制序列化。The following sections describe how to control serialization.

警告

二进制序列化可能会十分危险。Binary serialization can be dangerous. 请不要从不受信任的源反序列化数据,也不要将序列化数据往返到不受控制的系统。Never deserialize data from an untrusted source and never round-trip serialized data to systems not under your control.

重要

在早于 .NET Framework 4.0 的版本中,部分受信任的程序集中自定义用户数据的序列化是使用 GetObjectData 完成的。In versions previous to .NET Framework 4.0, serialization of custom user data in a partially trusted assembly was accomplished using the GetObjectData. 从版本 4.0 开始,该方法将标记有 SecurityCriticalAttribute 特性,该特性阻止在部分受信任的程序集中执行。Starting with version 4.0, that method is marked with the SecurityCriticalAttribute attribute which prevents execution in partially trusted assemblies. 若要解决此情况,请实现 ISafeSerializationData 接口。To work around this condition, implement the ISafeSerializationData interface.

在序列化期间和序列化之后运行自定义方法Running custom methods during and after serialization

最实用且最简便的方法(已引入 .NET Framework 2.0 版)是在序列化期间和序列化之后,将下列属性应用于更正数据所用的方法:The best practice and easiest way (introduced in version 2.0 of the .NET Framework) is to apply the following attributes to methods that are used to correct data during and after serialization:

这些属性允许类型参与序列化和反序列化过程中的任何一个阶段或所有四个阶段。These attributes allow the type to participate in any one of, or all four of the phases, of the serialization and deserialization processes. 这些特性为类型指定了应该在每个阶段调用的方法。The attributes specify the methods of the type that should be invoked during each phase. 这些方法不会访问序列化流,但是反而允许您在序列化或反序列化前后更改对象。The methods do not access the serialization stream but instead allow you to alter the object before and after serialization, or before and after deserialization. 可以在类型继承层次结构中的所有级别上应用这些特性,而每种方法在该层次结构中是按从基类型到派生程度最大的类型的顺序调用的。The attributes can be applied at all levels of the type inheritance hierarchy, and each method is called in the hierarchy from the base to the most derived. 这种机制使得序列化和反序列化可以负责到派生程度最大的实现,从而可以避免实现 ISerializable 接口时的复杂性和任何导致的问题。This mechanism avoids the complexity and any resulting issues of implementing the ISerializable interface by giving the responsibility for serialization and deserialization to the most derived implementation. 此外,这种机制允许格式化程序忽略字段的填充以及从序列化流中检索。Additionally, this mechanism allows the formatters to ignore the population of fields and retrieval from the serialization stream. 有关控制序列化和反序列化的详细信息和示例,请单击以上任一链接。For details and examples of controlling serialization and deserialization, click any of the previous links.

另外,向现有的可序列化类型中添加新字段时,可将 OptionalFieldAttribute 特性应用于该字段。In addition, when adding a new field to an existing serializable type, apply the OptionalFieldAttribute attribute to the field. 处理缺少新字段的流时,BinaryFormatterSoapFormatter 可以忽略不存在该字段的情况。The BinaryFormatter and the SoapFormatter ignores the absence of the field when a stream that is missing the new field is processed.

实现 ISerializable 接口Implementing the ISerializable interface

控制序列化的另一种方法是对某个对象实现 ISerializable 接口。The other way to control serialization is achieved by implementing the ISerializable interface on an object. 但请注意,上一节采用的方法会取代这种方法对序列化进行控制。Note, however, that the method in the previous section supersedes this method to control serialization.

除此之外,如果使用 Serializable 属性对某个类进行标记,且该类在类级别或对其构造函数具有声明性或命令性安全,则不应对该类执行默认的序列化。In addition, you should not use default serialization on a class that is marked with the Serializable attribute and has declarative or imperative security at the class level or on its constructors. 相反,这样的类应该始终实现 ISerializable 接口。Instead, these classes should always implement the ISerializable interface.

实现 ISerializable 涉及到实现 GetObjectData 方法以及反序列化对象时所用的特殊构造函数。Implementing ISerializable involves implementing the GetObjectData method and a special constructor that is used when the object is deserialized. 下面的示例代码演示如何对上一节中的 ISerializable 类实现 MyObjectThe following sample code shows how to implement ISerializable on the MyObject class from a previous section.

[Serializable]
public class MyObject : ISerializable
{
    public int n1;
    public int n2;
    public String str;

    public MyObject()
    {
    }

    protected MyObject(SerializationInfo info, StreamingContext context)
    {
      n1 = info.GetInt32("i");
      n2 = info.GetInt32("j");
      str = info.GetString("k");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("i", n1);
        info.AddValue("j", n2);
        info.AddValue("k", str);
    }
}
<Serializable()>  _
Public Class MyObject
    Implements ISerializable
    Public n1 As Integer
    Public n2 As Integer
    Public str As String

    Public Sub New()
    End Sub

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        n1 = info.GetInt32("i")
        n2 = info.GetInt32("j")
        str = info.GetString("k")
    End Sub 'New

    <SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _
    Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        info.AddValue("i", n1)
        info.AddValue("j", n2)
        info.AddValue("k", str)
    End Sub
End Class

序列化期间调用 GetObjectData 时,由用户负责填充随方法调用一起提供的 SerializationInfoWhen GetObjectData is called during serialization, you are responsible for populating the SerializationInfo provided with the method call. 将待序列化变量以名称和值对的形式添加。Add the variables to be serialized as name and value pairs. 任何文本都能用作名称。Any text can be used as the name. 如果对足够多的数据进行序列化,以在反序列化期间还原对象,您便可随意决定将哪些成员变量添加到 SerializationInfoYou have the freedom to decide which member variables are added to the SerializationInfo, provided that sufficient data is serialized to restore the object during deserialization. 如果基对象实现 ISerializable,则派生类应该对该基对象调用 GetObjectData 方法。Derived classes should call the GetObjectData method on the base object if the latter implements ISerializable.

请注意,序列化可以允许其他代码查看或修改用其他方式无法访问的对象实例数据。Note that serialization can allow other code to see or modify object instance data that is otherwise inaccessible. 因此,执行序列化的代码需使用指定了 SerializationFormatter 标志的 SecurityPermissionTherefore, code that performs serialization requires the SecurityPermission with the SerializationFormatter flag specified. 在默认策略下,通过 Internet 下载的代码或 Intranet 代码不会授予该权限;只有本地计算机上的代码才被授予该权限。Under default policy, this permission is not given to Internet-downloaded or intranet code; only code on the local computer is granted this permission. 必须采用下列方式显式保护 GetObjectData 方法:要求使用使用指定了 SerializationFormatter 标志的 SecurityPermission,或者要求具备专门用于帮助保护私有数据的其他权限。The GetObjectData method must be explicitly protected either by demanding the SecurityPermission with the SerializationFormatter flag specified or by demanding other permissions that specifically help protect private data.

若私有字段存储的是敏感信息,应该要求对 GetObjectData 具备相应权限,以便保护该数据。If a private field stores sensitive information, you should demand the appropriate permissions on GetObjectData to protect the data. 请记住,如果已向代码授予指定了 SerializationFormatter 标志的 SecurityPermission,则该代码可查看和修改私有字段中存储的数据。Remember that code that has been granted SecurityPermission with the SerializationFormatter flag specified can view and modify the data stored in private fields. 被授予此 SecurityPermission 的恶意调用方可以查看相关数据(如隐藏的目录位置或授予的权限),还可以通过这些数据利用计算机中的安全漏洞。A malicious caller granted this SecurityPermission can view data such as hidden directory locations or granted permissions and use the data to exploit a security vulnerability on the computer. 有关可以指定的安全权限标志的完整列表,请参阅 SecurityPermissionFlag 枚举For a complete list of the security permission flags you can specify, see the SecurityPermissionFlag Enumeration.

强调何时将 ISerializable 添加到必须同时实现 GetObjectData 和特殊构造函数的某个类,这一点很重要。It's important to stress that when ISerializable is added to a class you must implement both GetObjectData and the special constructor. 如果缺少 GetObjectData,编译器会发出警告。The compiler warns you if GetObjectData is missing. 但是,鉴于无法强制实现构造函数,如果不存在该构造函数,则不会发出任何警告,但此时如果尝试对某个类进行反序列化,将会引发异常。However, because it is impossible to enforce the implementation of a constructor, no warning is provided if the constructor is absent, and an exception is thrown when an attempt is made to deserialize a class without the constructor.

若要解决潜在的安全和版本化问题,当前的设计应该优先于 SetObjectData 方法。The current design was favored above a SetObjectData method to get around potential security and versioning problems. 例如,如果将 SetObjectData 方法定义为接口的组成部分,则该方法必须是公共方法。因此,用户必须编写代码,以防多次调用 SetObjectData 方法。For example, a SetObjectData method must be public if it is defined as part of an interface; thus users must write code to defend against having the SetObjectData method called multiple times. 否则,在执行某项操作的过程中,对某个对象调用 SetObjectData 方法的恶意应用程序可能会导致潜在的问题。Otherwise, a malicious application that calls the SetObjectData method on an object in the process of executing an operation can cause potential problems.

在反序列化期间,使用为这个目的提供的构造函数将 SerializationInfo 传递到类。During deserialization, SerializationInfo is passed to the class using the constructor provided for this purpose. 反序列化该对象时,将会忽略对该构造函数施加的任何可见性限制。因此,可将该类标记为公共类、受保护的类、内部类或私有类。Any visibility constraints placed on the constructor are ignored when the object is deserialized; so you can mark the class as public, protected, internal, or private. 但是,除非密封了该类,否则最好将构造函数设为受保护的函数;如果密封了该类,应将构造函数标记为私有函数。However, it is a best practice to make the constructor protected unless the class is sealed, in which case the constructor should be marked private. 构造函数还应该执行彻底的输入验证。The constructor should also perform thorough input validation. 为避免被恶意代码误用,构造函数应该强制实施安全性检查和权限,而这些安全性检查和权限也是使用其他任何构造函数获取该类的实例所必需的。To avoid misuse by malicious code, the constructor should enforce the same security checks and permissions required to obtain an instance of the class using any other constructor. 如果不采纳上述建议,恶意代码会跳过使用公共构造函数在标准实例构造期间应用的所有安全性,预序列化对象,获取对使用指定了 SerializationFormatter 标志的 SecurityPermission 的控制,并反序列化客户端计算机上的对象。If you do not follow this recommendation, malicious code can preserialize an object, obtain control with the SecurityPermission with the SerializationFormatter flag specified and deserialize the object on a client computer bypassing any security that would have been applied during standard instance construction using a public constructor.

若要还原对象的状态,只需使用序列化期间采用的名称从 SerializationInfo 中检索变量值即可。To restore the state of the object, simply retrieve the values of the variables from SerializationInfo using the names used during serialization. 如果基类实现 ISerializable,则应调用基构造函数,以使基对象可以还原其变量。If the base class implements ISerializable, the base constructor should be called to allow the base object to restore its variables.

从实现 ISerializable 的类派生新类时,如果派生类的变量需要进行序列化,则该派生类必须同时实现构造函数和 GetObjectData 方法。When you derive a new class from one that implements ISerializable, the derived class must implement both the constructor as well as the GetObjectData method if it has variables that need to be serialized. 下面的代码示例演示如何使用前面说明的 MyObject 类完成此项操作。The following code example shows how this is done using the MyObject class shown previously.

[Serializable]
public class ObjectTwo : MyObject
{
    public int num;

    public ObjectTwo()
      : base()
    {
    }

    protected ObjectTwo(SerializationInfo si, StreamingContext context)
      : base(si, context)
    {
        num = si.GetInt32("num");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo si, StreamingContext context)
    {
        base.GetObjectData(si,context);
        si.AddValue("num", num);
    }
}
<Serializable()>  _
Public Class ObjectTwo
    Inherits MyObject
    Public num As Integer

    Public Sub New()

    End Sub

    Protected Sub New(ByVal si As SerializationInfo, _
    ByVal context As StreamingContext)
        MyBase.New(si, context)
        num = si.GetInt32("num")
    End Sub

    <SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _
    Public Overrides Sub GetObjectData(ByVal si As SerializationInfo, ByVal context As StreamingContext)
        MyBase.GetObjectData(si, context)
        si.AddValue("num", num)
    End Sub
End Class

不要忘记在反序列化构造函数中调用基类。Don't forget to call the base class in the deserialization constructor. 如果没有完成此操作,决不会调用基类上的构造函数,也不会在反序列化之后完全构造该对象。If this isn't done, the constructor on the base class is never called, and the object is not fully constructed after deserialization.

对象是从内到外重新构造的;在反序列化期间调用方法时,可能会产生非预期的副作用,原因是调用的方法引用的可能是执行调用时尚未反序列化的对象引用。Objects are reconstructed from the inside out; and calling methods during deserialization can have undesirable side effects, because the methods called might refer to object references that have not been deserialized by the time the call is made. 如果正在被反序列化的类实现 IDeserializationCallback,则反序列化整个对象图后,将会自动调用 OnDeserialization 方法。If the class being deserialized implements the IDeserializationCallback, the OnDeserialization method is automatically called when the entire object graph has been deserialized. 此时,便已完全还原引用的所有子对象。At this point, all the child objects referenced have been fully restored. 哈希表是类的典型示例,在不使用事件侦听器的情况下,很难对哈希表进行反序列化。A hash table is a typical example of a class that is difficult to deserialize without using the event listener. 虽然在反序列化期间易于检索键和值对,但是,将这些对象重新添加到哈希表时,可能会引起问题,原因是不能保证派生自哈希表的类已被反序列化。It is easy to retrieve the key and value pairs during deserialization, but adding these objects back to the hash table can cause problems, because there is no guarantee that classes that derived from the hash table have been deserialized. 因此,不建议在这个阶段对哈希表调用方法。Calling methods on a hash table at this stage is therefore not advisable.

请参阅See also