Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции OOP c#.doc
Скачиваний:
44
Добавлен:
22.09.2019
Размер:
3.38 Mб
Скачать

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

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

Среда .NET Framework обладает развитым механизмом поддержки сериализации, включая поддержку сериализации в различных форматах. Основные классы, связанные с сериализацией, размещены в наборе пространств имен вида System.Runtime.Serialization.* и System.Xml.Serialization.*. Так, пространство имен System.Runtime.Serialization.Formatters.Binary обеспечивает поддержку сериализации в двоичном формате. Класс BinaryFormatter из этого пространства имен способен выполнить сериализацию графа объектов в поток при помощи метода Serialize() и десериализацию при помощи метода Deserialize().

Рассмотрим сериализацию на примере. Пусть имеется класс с информацией о студенте (имя, возраст, средний балл), а также класс с информацией о группе (список студентов и средний балл группы). Эти классы могут быть описаны следующим образом:

class Student {

public string Name;

public int Age;

public double MeanScore;

public Student(string Name, int Age, double MeanScore) {

this.Name = Name;

this.Age = Age;

this.MeanScore = MeanScore;

}

}

class Group {

public ArrayList GL = new ArrayList();

public double MSG;

public Student BestStudent;

public double CalcMSG() {

double sum = 0;

foreach(Student s in GL)

sum += s.MeanScore;

MSG = sum / GL.Count;

return MSG;

}

public Student FindTheBest() {

BestStudent = (Student)GL[0];

foreach(Student s in GL)

if (s.MeanScore > BestStudent.MeanScore)

BestStudent = s;

return BestStudent;

}

}

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

[Serializable]

class Student {

. . .

}

[Serializable]

class Group {

public ArrayList GL = new ArrayList();

[NonSerialized] // Не надо сохранять – просто посчитаем

public double MSG;

. . .

}

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

// Нам понадобятся следующие пространства имен

using System;

using System.Collections;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

. . .

// Создаем группу и добавляем в нее несколько студентов

Group g = new Group();

g.GL.Add(new Student("Vova", 20, 4.5));

g.GL.Add(new Student("Ira", 20, 5));

g.GL.Add(new Student("Peter", 19, 4));

// Выводим некоторую информацию о группе

Console.WriteLine(g.CalcMSG());

Console.WriteLine(g.FindTheBest().Name);

// Создаем поток – это будет файл

Stream fs = new FileStream("data.dat", FileMode.Create);

// Создаем объект для сериализации в двоичном формате

BinaryFormatter fmt = new BinaryFormatter();

// Сериализуем и затем закрываем поток

fmt.Serialize(fs, g);

fs.Close();

// Теперь десериализация. Создаем поток

fs = new FileStream("data.dat", FileMode.Open);

// Десериализация. Обратите внимание на приведение типов

Group d = (Group)fmt.Deserialize(fs);

// Выводим информацию о группе

Console.WriteLine(d.CalcMSG());

Console.WriteLine(d.FindTheBest().Name);

Метод Deserialize() получает в качестве параметра поток, из которого десериализуется объект и возвращает объект типа object. В одном потоке можно сериализовать несколько различных объектов – главное, чтобы последовательность десериализации соответствовала сериализации.

Десериализацию удобно представлять как своеобразный вызов конструктора, так как результатом десериализации является ссылка на существующий объект. Однако если некоторые поля класса были помечены как [NonSerialized], то возможно после десериализации потребуется просчитать значения данных полей. Допустимое решение – реализовать в классе интерфейс IDeserializationCallback из пространства имен System.Runtime.Serialization. Данный интерфейс содержит единственный метод – OnDeserialization, который вызывается исполняемой средой автоматически после десериализации объекта. Используем интерфейс для класса Group:

[Serializable]

class Group : IDeserializationCallback {

public ArrayList GL = new ArrayList();

// Не будем сохранять средний балл и лучшего студента

[NonSerialized]

public double MSG;

[NonSerialized]

public Student BestStudent;

public double CalcMSG() { . . . }

public Student FindTheBest() { . . . }

// После десериализации просчитаем средний балл и

// найдем лучшего студента. Работа с параметром метода

// исполняемой средой на данный момент не поддерживается!

public void OnDeserialization(object o) {

CalcMSG();

FindTheBest();

}

}

Рассмотрим несколько примеров сериализации в различных форматах. Класс SoapFormatter из пространства имен System.Runtime.Serialization.Formatters.Soap обеспечивает сериализацию объекта в формате протокола SOAP (для использования данного пространства имен требуется подключить библиотеку system.runtime.serialization.formatters.soap.dll). Изменения в коде примера минимальны:

using System.Runtime.Serialization.Formatters.Soap;

. . .

// Создаем объект для сериализации в формате SOAP

SoapFormatter fmt = new SoapFormatter();

. . .

Файл в формате SOAP – это xml-файл с дополнительной информацией протокола SOAP. В .NET Framework можно выполнить сериализацию объектов в формате XML. Данный функционал обеспечивает класс XmlSerializer из пространства имен System.Xml.Serialization (файл System.Xml.dll). При XML-сериализации сохраняются данные объекта, доступные через public-свойства и поля. Кроме этого, сериализуемый класс должен иметь конструктор без параметров и являться public-классом.

Гибкая настройка XML-сериализации может быть выполнена при помощи атрибутов. Рассмотрим некоторые из атрибутов. Атрибут XmlRootAttribute применяется к классу и помогает определить корневой элемент в XML-файле. Поля и свойства, которые не должны сохраняться, помечаются атрибутом XmlIgnoreAttribute. Если класс агрегирует объекты других классов, то эти классы должны быть указаны при помощи атрибута XmlIncludeAttribute. Атрибут XmlAttributeAttribute используется, если элемент класса требуется сохранить в виде атрибута XML-тэга. Если класс агрегирует массив объектов, то настройка вида сохранения этого массива выполняется при помощи атрибутов XmlArrayAttribute и XmlArrayItemAttribute.

Рассмотрим пример XML-сериализации. Будем сохранять в формате XML данные классов Student и Group. При описании этих классов воспользуемся некоторыми атрибутами. Ниже представлен код программы, выполняющей создание объектов, сериализацию и десериализацию.

using System;

using System.Collections;

using System.IO;

using System.Xml.Serialization;

public class Student {

public string Name;

[XmlAttribute("Age")]

public int Age;

public double MeanScore;

public Student() { }

public Student(string Name, int Age, double MeanScore) {

this.Name = Name;

this.Age = Age;

this.MeanScore = MeanScore;

}

}

[XmlRoot("StudentGroup")]

[XmlInclude(typeof(Student))]

public class Group {

[XmlArray("GroupList")]

[XmlArrayItem("Student")]

public ArrayList GL = new ArrayList();

[XmlIgnore]

public double MSG;

public Group() { }

public double CalcMSG() {

double sum = 0;

foreach (Student s in GL)

sum += s.MeanScore;

MSG = sum / GL.Count;

return MSG;

}

}

class Program {

static void Main(string[] args) {

Group g = new Group();

g.GL.Add(new Student("Ivanov", 20, 6.0));

g.GL.Add(new Student("Petrov", 21, 7.0));

Stream fs= new FileStream("data.dat",FileMode.Create);

// Указываем тип того объекта, который сериализуем

XmlSerializer x = new XmlSerializer(typeof(Group));

// Сериализуем

x.Serialize(fs, g);

fs.Close();

// Восстанавливаем

fs = new FileStream("data.dat", FileMode.Open);

Group d = (Group)x.Deserialize(fs);

fs.Close();

}

}

XML-файл с сохраненной информацией выглядит следующим образом:

<?xml version="1.0"?>

<StudentGroup

xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance

xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<GroupList>

<Student xsi:type="Student" Age="20">

<Name>Ivanov</Name>

<MeanScore>6</MeanScore>

</Student>

<Student xsi:type="Student" Age="21">

<Name>Petrov</Name>

<MeanScore>7</MeanScore>

</Student>

</GroupList>

</StudentGroup>