Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Базовые технологии платформы .NET.docx
Скачиваний:
13
Добавлен:
03.11.2018
Размер:
614.46 Кб
Скачать

20. Сериализация

Сериализация ‑ это процесс, при котором данные объекта переносятся из памяти в поток данных для сохранения. Десериализация – обратный процесс, заключающийся в восстановлении состояния объекта из потока. При выполнении сериализации следует учитывать несколько нетривиальных моментов. Например, сохранение полей объекта требует сохранения всех данных агрегируемых объектов – то есть требуется сохранить граф зависимых объектов.

Платформа .NET предлагает три основных механизма сериализации:

1. Сериализация времени выполнения.

2. Сериализация контрактов данных.

3. XML-сериализация.

При рассмотрении примеров сериализации будем использовать классы, описывающие одного студента и группу студентов.

public class Student : IComparable<Student>

{

public string Name { get; set; }

public int Age { get; set; }

public double GPA { get; set; } // Grade Point Average, средний балл

public int CompareTo(Student other)

{

return GPA.CompareTo(other.GPA);

}

}

public class Group : Collection<Student>

{

public Student BestStudent; // поле, а не свойство

public double GPA { get; private set; }

public double CalculateGPA()

{

return GPA = Items.Select(student => student.GPA).Average();

}

public Student FindTheBest()

{

return BestStudent = Items.Max();

}

}

// пример использования классов Student и Group

var group = new Group {

new Student {Name = "Smirnov", Age = 18, GPA = 9},

new Student {Name = "Ivanova", Age = 20, GPA = 6.67},

new Student {Name = "Kuznetsov", Age = 18, GPA = 6},

new Student {Name = "Sokolov", Age = 20, GPA = 7.67},

new Student {Name = "Lebedeva", Age = 20, GPA = 9}};

Сериализация времени выполнения

Основные классы, связанные с сериализацией времени выполнения (runtime serialization), размещены в пространствах имён с префиксом System.Runtime.Serialization. Например, сериализацию в двоичном формате обеспечивают классы из System.Runtime.Serialization.Formatters.Binary.

Сериализация времени выполнения применима только к объектам сериализуемых типов. Сериализуемый тип – это тип, помеченный атрибутом [Serializable]1, у которого все поля имеют сериализумый тип. Все базовые типы платформы .NET являются сериализуемыми. Если планируется сериализация объекта group, необходимо добавить атрибут [Serializable] к классам Group и Student. Сериализация некоторых полей может не иметь смысла (например, эти поля вычисляются при работе с объектом или хранят конфиденциальные данные). Для таких полей можно применить атрибут [NonSerialized].

Изменим код классов Group и Student с учётом вышесказанного:

[Serializable]

public class Student : IComparable<Student>

{

// элементы класса не показаны

}

[Serializable]

public class Group : Collection<Student>

{

[NonSerialized] // атрибут применяется к полю, а не к свойству

public Student BestStudent;

// неизменившиеся элементы класса не показаны

}

Сериализуемые типы можно сохранить в поток в различных форматах. Платформа .NET имеет классы, поддерживающие двоичный формат и формата SOAP2. При необходимости разработчик может создать класс для поддержки собственного формата сериализации.

Осуществим сериализацию объекта group в двоичном формате. Используем для этого экземплярный метод Serialize() класса BinaryFormatter. Метод принимает два аргумента: поток сериализации и сериализуемый объект:

var formatter = new BinaryFormatter();

using (Stream s = File.Create("group.dat"))

{

formatter.Serialize(s, group);

}

Метод Deserialize() класса BinaryFormatter выполняет десериализацию:

using (Stream s = File.OpenRead("group.dat"))

{

group = (Group)formatter.Deserialize(s);

}

Метод десериализации размещает объект в памяти и возвращает ссылку на него. При этом конструкторы типа не вызываются. Это может стать проблемой, если нужна особая инициализация объекта и восстановление [NonSerialized]-полей. Тип может реализовать интерфейс IDeserializationCallback, который содержит единственный метод OnDeserialization(), вызываемый автоматически после десериализации объекта.

[Serializable]

public class Group : Collection<Student>, IDeserializationCallback

{

// после десериализации заполним поле BestStudent

// CLR не поддерживает работу с параметром sender

void OnDeserialization(object sender)

{

FindTheBest();

}

// неизменившиеся элементы класса не показаны

}

Альтернативой IDeserializationCallback являются атрибуты [OnSerializing], [OnSerialized], [OnDeserializing], [OnDeserialized], применимые к методам. Помеченные методы вызываются автоматически до и после сериализации или десериализации соответственно. Метод, который помечен одним из указанных атрибутов, должен принимать в качестве аргумента объект класса StreamingContext1 и не возвращать значений. Каждый из атрибутов может применяться только к одному методу в типе.

[Serializable]

public class Group : Collection<Student>

{

[OnDeserialized]

private void AfterDeserialization(StreamingContext context)

{

FindTheBest();

}

// неизменившиеся элементы класса не показаны

}

Атрибут [OptionalField] применяется к полю и подавляет при десериализации генерацию исключения, если помеченное поле не найдено в потоке данных. Это позволяет сохранять «старые» объекты, затем модифицировать тип, расширяя состав его полей, и десериализовать данные в «новые» объекты типа.

Если программиста не устраивает способ организации потока сериализуемых данных, он может повлиять на этот процесс, реализуя в сериализуемом типе интерфейс ISerializable:

public interface ISerializable

{

void GetObjectData(SerializationInfo info,

StreamingContext context);

}

Интерфейс позволяет выполнить любые действия, связанные с формированием данных для сохранения. Метод GetObjectData() вызывается CLR автоматически при выполнении сериализации. Реализация метода подразумевает заполнение объекта SerializationInfo набором данных вида «ключ-значение», которые (обычно) соответствуют полям сохраняемого объекта. Класс SerializationInfo содержит перегруженный метод AddValue(), набор методов вида GetПримитивныйТип(), а также свойства для указания имени типа и сборки сериализуемого объекта. Если тип реализует интерфейс ISerializable, он должен содержать специальный private-конструктор, который будет вызывать CLR после выполнения десериализации. Конструктор должен иметь параметр типа SerializationInfo и параметр типа StreamingContext.

Рассмотрим пример реализации ISerializable в классе Student.

[Serializable]

public class Student : IComparable<Student>, ISerializable

{

// не показаны свойства и метод CompareTo()

// неявная реализация интерфейса ISerializable

void ISerializable.GetObjectData(SerializationInfo info,

StreamingContext ctx)

{

info.SetType(typeof(Student));

info.AddValue("Name", Name);

info.AddValue("Age", Age);

info.AddValue("Mark", (int)(GPA * 10));

}

private Student(SerializationInfo info, StreamingContext ctx)

{

Name = info.GetString("Name");

Age = info.GetInt32("Age");

GPA = info.GetInt32("Mark") / 10.0;

}

public Student() { }

}