Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C#, 320 стр..doc
Скачиваний:
8
Добавлен:
16.11.2018
Размер:
3.64 Mб
Скачать

Сериализация объектов

При работе с программной системой зачастую возникает необходимость в сериализации объектов. Под сериализацией понимают процесс сохранения объектов в долговременной памяти (файлах) в период выполнения системы. Под десериализацией понимают обратный процесс - восстановление состояния объектов, хранимых в долговременной памяти. Механизмы сериализации C# и Framework .Net поддерживают два формата сохранения данных - в бинарном файле и XML-файле. В первом случае данные при сериализации преобразуются в бинарный поток символов, который при десериализации автоматическипреобразуется в нужное состояние объектов. Другой возможный преобразователь (SOAP formatter) запоминает состояние объекта в формате xml.

Сериализация позволяет запомнить рубежные состояния системы объектов с возможностью последующего возвращения к этим состояниям. Она необходима, когда завершение сеанса работы не означает завершение вычислений. В этом случае очередной сеанс работы начинается с восстановления состояния, сохраненного в конце предыдущего сеанса работы. Альтернативой сериализации является работа с обычной файловой системой, с базами данных и другими хранилищами данных. Поскольку механизмы сериализации, предоставляемые языком C#, эффективно поддерживаются .Net Framework, то при необходимости сохранения данных значительно проще и эффективнее пользоваться сериализацией, чем самому организовывать их хранение и восстановление.

Еще одно важное применение сериализации - это обмен данными удаленных систем. При удаленном обмене данными предпочтительнее формат xml из-за открытого стандарта передачи данных в Интернете по soap-протоколу, из-за открытого стандарта на структуру xml-документов. Обмен становится достаточно простым даже для систем, построенных на разных платформах и в разных средах разработки.

Так же, как и клонирование, сериализация может быть поверхностной, когда сериализуется на одном шаге единственный объект, и глубокой, когда, начиная с корневого объекта, сериализуется совокупность объектов, связанных взаимными ссылками (граф объектов). Глубокую сериализацию, часто обязательную, самому организовать непросто, так как она требует, как правило, рекурсивного обхода структуры объектов.

Если класс объявить с атрибутом [Serializable], то в него встраивается стандартный механизм сериализации, поддерживающий, что крайне приятно, глубокую сериализацию. Если по каким-либо причинам стандартная сериализация нас не устраивает, то класс следует объявить наследником интерфейса ISerialzable, реализация методов которого позволит управлять процессом сериализации. Мы рассмотрим обе эти возможности.

Класс с атрибутом сериализации

Класс, объекты которого предполагается сериализовать стандартным образом, должен при объявлении сопровождаться атрибутом [Serializable]. Стандартная сериализация предполагает два способа сохранения объекта: в виде бинарного потока символов и в виде xml-документа. В бинарном потоке сохраняются все поля объекта, как открытые, так и закрытые. Процессом этим можно управлять, помечая некоторые поля класса атрибутом [NonSerialized] - эти поля сохраняться не будут:

[Serializable]

public class Test

{

public string name;

[NonSerialazed] int id;

int age;

//другие поля и методы класса

}

В класс Test встроен стандартный механизм сериализации его объектов. При сериализации поля name и age будут сохраняться, поле id - нет.

Для запуска механизма необходимо создать объект, называемый форматером и выполняющий сериализацию и десериализацию данных с подходящим их форматированием. Библиотека FCL предоставляет два класса форматеров. Бинарный форматер, направляющий данные в бинарный поток, принадлежит классу BinaryFormatter. Этот класс находится в пространстве имен библиотеки FCL:

System.Runtime.Serialization.Formatters.Binary

Давайте разберемся, как устроен этот класс. Он является наследником двух интерфейсов: IFormatter и IRemotingFormatter. Интерфейс IFormatter имеет два открытых метода: Serialize и Deserialize, позволяющих сохранять и восстанавливать всю совокупность связанных объектов с заданным объектом в качестве корня. Интерфейс IRemotingFormatter имеет те же открытые методы: Serialize и Deserialize, позволяющие выполнять глубокую сериализацию, но в режиме удаленного вызова. Поскольку сигнатуры одноименных методов интерфейсов отличаются, то конфликта имен при наследовании не происходит - в классе BinaryFormatter методы Serialize и Deserialize перегружены. Для удаленного вызова задается дополнительный параметр, что и позволяет различать, локально или удаленно выполняются процессы обмена данными.

В пространстве имен библиотеки FCL:

System.Runtime.Serialization.Formatters.Soap

находится класс SoapFormatter. Он является наследником тех же интерфейсов IFormatter и IRemotingFormatter и реализует их методы Serialize и Deserialize, позволяющие выполнять глубокую сериализацию и десериализацию при сохранении данных в формате xml. Помимо методов класса SoapFormatter, xml-сериализацию можно выполнять средствами другого класса -- XmlSerializer.

Из новых средств, еще не рассматривавшихся в наших лекциях, для организации сериализации понадобятся файлы. Пространство имен IO библиотеки FCL предоставляет классы, поддерживающие ввод-вывод данных. В частности, в этом пространстве есть абстрактный класс Stream для работы с потоками данных. С одним из его потомков - классом FileStream - мы и будем работать в нашем примере.

В качестве примера промоделируем сказку Пушкина "О рыбаке и рыбке". Как вы помните, жадная старуха богатела, богатела, но после очередного желания оказалась у разбитого корыта, вернувшись в начальное состояние. Сериализация позволит нам запомнить начальное состояние, меняющееся по мере выполнения рыбкой первых пожеланий рыбака и его старухи. Десериализация вернет все в начальное состояние. Опишу класс, задающий героев пушкинской сказки:

[Serializable]

public class Personage

{

public Personage(string name, int age)

{

this.name = name; this.age = age;

}

//поля класса

static int wishes;

public string name, status, wealth;

int age;

public Personage couple;

//методы класса

}

Герои сказки - объекты этого класса обладают свойствами, задающими имя, возраст, статус, имущество и супруга. Имя и возраст задаются в конструкторе класса, а остальные свойства задаются в следующем методе:

public void marry (Personage couple)

{

this.couple = couple;

couple.couple = this;

this.status ="крестьянин";

this.wealth ="рыбацкая сеть";

this.couple.status = "крестьянка";

this.couple.wealth = "корыто";

SaveState();

}

Предусловие метода предполагает, что метод вызывается один раз главным героем (рыбаком). В методе устанавливаются взаимные ссылки между героями сказки, их начальное состояние. Завершается метод сохранением состояния объектов, выполняемого при вызове метода SaveState:

void SaveState()

{

BinaryFormatter bf = new BinaryFormatter();

FileStream fs = new FileStream

("State.bin",FileMode.Create, FileAccess.Write);

bf.Serialize(fs,this);

fs.Close();

}

Здесь и выполняется сериализация графа объектов. Как видите, все просто. Вначале создается форматер - объект bf класса BinaryFormatter. Затем определяется файл, в котором будет сохраняться состояние объектов, - объект fs класса FileStream. Заметьте, в конструкторе файла, кроме имени файла, указываются его характеристики: статус, режим доступа. На деталях введения файлов я останавливаться не буду. Теперь, когда основные объекты определены, остается вызвать метод Serialize объекта bf, которому в качестве аргументов передается объект fs и текущий объект, представляющий корневой объект графа объектов, которые подлежат сериализации. Глубокая сериализация, реализуемая в данном случае, не потребовала от нас никаких усилий.

Нам понадобится еще метод, описывающий жизнь героев сказки:

public Personage AskGoldFish()

{

Personage fisher = this;

if (fisher.name == "рыбак")

{

wishes++;

switch (wishes)

{

case 1: ChangeStateOne();break;

case 2: ChangeStateTwo();break;

case 3: ChangeStateThree();break;

default: BackState(ref fisher);break;

}

}

return(fisher);

}//AskGoldFish

Метод реализует анализ желаний героини сказки. Первые три желания исполняются, и состояние героев меняется:

void ChangeStateOne()

{

this.status = "муж дворянки";

this.couple.status = "дворянка";

this.couple.wealth = "имение";

}

void ChangeStateTwo()

{

this.status = "муж боярыни";

this.couple.status = "боярыня";

this.couple.wealth = "много поместий";

}

void ChangeStateThree()

{

this.status = "муж государыни";

this.couple.status = "государыня";

this.couple.wealth = "страна";

}

Начиная с четвертого желания, все возвращается в начальное состояние - выполняется десериализация графа объектов:

void BackState(ref Personage fisher)

{

BinaryFormatter bf = new BinaryFormatter();

FileStream fs = new FileStream

("State.bin",FileMode.Open, FileAccess.Read);

fisher = (Personage)bf.Deserialize(fs);

fs.Close();

}

Обратите внимание, что у метода есть аргумент, передаваемый по ссылке. Этот аргумент получает значение - ссылается на объект, создаваемый методом Deserialize. Без аргумента метода не обойтись, поскольку возвращаемый методом объект нельзя присвоить текущему объекту this. Важно также отметить, что метод Deserialize восстанавливает весь граф объектов, возвращая в качестве результата корень графа.

В классе определен еще один метод, сообщающий о текущем состоянии объектов:

public void About()

{

Console.WriteLine("имя = {0}, возраст = {1},"+

"статус = {2}, состояние ={3}",name,age,status, wealth);

Console.WriteLine("имя = {0}, возраст = {1}," +

"статус = {2}, состояние ={3}", this.couple.name,

this.couple.age,this.couple.status, this.couple.wealth);

}

Для завершения сказки нам нужно в клиентском классе создать ее героев:

public void TestGoldFish()

{

Personage fisher = new Personage("рыбак", 70);

Personage wife = new Personage("старуха", 70);

fisher.marry(wife);

Console.WriteLine("До золотой рыбки"); fisher.About();

fisher = fisher.AskGoldFish();

Console.WriteLine("Первое желание"); fisher.About();

fisher = fisher.AskGoldFish();

Console.WriteLine("Второе желание"); fisher.About();

fisher = fisher.AskGoldFish();

Console.WriteLine("Третье желание"); fisher.About();

fisher = fisher.AskGoldFish();

Console.WriteLine("Еще хочу"); fisher.About();

fisher = fisher.AskGoldFish();

Console.WriteLine("Хочу, но уже поздно"); fisher.About();

}

На рис. 19.6 показаны результаты исполнения сказки.

Рис. 19.6.  Сказка о рыбаке и рыбке

Что изменится, если перейти к сохранению данных в xml-формате? немногое. Нужно лишь заменить объявление форматера:

void SaveStateXML()

{

SoapFormatter sf = new SoapFormatter();

FileStream fs = new FileStream

("State.xml",FileMode.Create, FileAccess.Write);

sf.Serialize(fs,this);

fs.Close();

}

void BackStateXML(ref Personage fisher)

{

SoapFormatter sf = new SoapFormatter();

FileStream fs = new FileStream

("State.xml",FileMode.Open, FileAccess.Read);

fisher = (Personage)sf.Deserialize(fs);

fs.Close();

}

Клиент, работающий с объектами класса, этих изменений и не почувствует. Результаты вычислений останутся теми же, что и в предыдущем случае. Правда, файл, сохраняющий данные, теперь выглядит совсем по-другому. Это обычный xml-документ, который мог быть создан в любом из приложений. Вот как выглядит этот документ, открытый в браузере Internet Explorer.

Рис. 19.7.  XML-документ, сохраняющий состояние объектов

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]