Пользовательская сериализация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. BinaryFormatter и SoapFormatter игнорируют отсутствие поля, если обрабатывается поток без нового поля.The BinaryFormatter and the SoapFormatter ignores the absence of the field when a stream that is missing the new field is processed.

Реализация интерфейса ISerializableImplementing 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 в классе MyObject на основе информации предыдущего раздела.The 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, следует указать информацию SerializationInfo, которая предоставляется вместе с вызовом метода.When 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. Переменные-члены, добавляемые в SerializationInfo могут быть любыми при условии, что сериализуется достаточное количество данных для восстановления объекта при десериализации.You 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. Производные классы должны вызывать метод GetObjectData для базового объекта, если в последнем реализован интерфейс ISerializable.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. Поэтому код, выполняющий сериализацию, требует разрешения SecurityPermission с установленным флагом SerializationFormatter.Therefore, code that performs serialization requires the SecurityPermission with the SerializationFormatter flag specified. При политике безопасности по умолчанию такое разрешение не предоставляется коду, загруженному из Интернета или интрасети, и дается только коду на локальном компьютере.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 должен быть явно защищен посредством запроса либо SecurityPermission с установленным флагом SerializationFormatter, либо иных разрешений, которые предназначены специально для защиты закрытых данных.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. Помните, что код, которому предоставлены разрешения SecurityPermission с установленным флагом SerializationFormatter, может просматривать и изменять данные, которые хранятся в закрытых полях.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 Enumeration.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. При несоблюдении этой рекомендации вредоносный код может предварительно сериализовать объект, получить контроль над SecurityPermission с установленным флагом SerializationFormatter и десериализовать объект на клиентском компьютере, обойдя любую защиту, которая была реализована во время построения стандартного экземпляра с использованием открытого конструктора.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