Чтение онлайн

ЖАНРЫ

Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:

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-документ, сохраняющий состояние объектов

Интерфейс ISerializable

При необходимости можно самому управлять процессом сериализации. В этом случае наш класс должен быть наследником интерфейса ISerializable. Класс, наследующий этот интерфейс, должен реализовать единственный метод этого интерфейса GetObjectData и добавить защищенный конструктор. Схема сериализации и десериализации остается и в этом случае той же самой. Можно использовать как бинарный форматер, так и soap-форматер. Но теперь метод Serialize использует не стандартную реализацию, а вызывает метод GetObjectData , управляющий записью данных. Метод Deserialize, в свою очередь, вызывает защищенный конструктор, создающий объект и заполняющий его поля сохраненными значениями.

Конечно, возможность управлять сохранением и восстановлением данных дает большую гибкость и позволяет, в конечном счете, уменьшить размер файла, хранящего данные, что может быть крайне важно, особенно если речь идет об обмене данными с удаленным приложением. Если речь идет о поверхностной сериализации, то атрибут NonSerialized, которым можно помечать поля, не требующие сериализации, как правило, достаточен для управления эффективным сохранением данных. Так что управлять имеет смысл только глубокой сериализацией, когда сохраняется и восстанавливается граф объектов. Но, как уже говорилось, это может быть довольно сложным занятием, что будет видно и для нашего простого примера с рыбаком и рыбкой.

Рассмотрим, как устроен метод GetObjectData , управляющий сохранением данных. У этого метода два аргумента:

GetObjectData (Serializedlnfo info, StreamingContext context)

Поскольку самому вызывать этот метод не приходится — он вызывается автоматически методом Serialize, то можно не особенно задумываться о том, как создавать аргументы метода. Более важно понимать, как их следует использовать. Чаще всего используется только аргумент info и его метод AddVaiue (key, field). Данные сохраняются вместе с ключом, используемым позже при чтении данных. Аргумент key, который может быть произвольной строкой, задает ключ, а аргумент field — поле объекта. Например, для сохранения полей name и аде можно задать следующие операторы:

info.AddValue("name",name); infо. AddValue("age", age);

Поскольку имена полей уникальны, то их разумно использовать в качестве ключей.

Если поле son класса Father является объектом класса child и этот класс сериализуем, то для сохранения объекта son следует вызвать метод:

son.GetObjectData(info, context)

Если не возникает циклов, причиной которых являются взаимные ссылки, то особых сложностей с сериализацией и десериализацией не возникает. Взаимные ссылки осложняют картину и требуют индивидуального подхода к решению. На последующем примере мы покажем, как

можно справиться с этой проблемой в конкретном случае.

Перейдем теперь к рассмотрению специального конструктора класса. Он может быть объявлен с атрибутом доступа private, но лучше, как и во многих других случаях, применять атрибут protected, что позволит использовать этот конструктор потомками класса, осуществляющими собственную сериализацию. У конструктора те же аргументы, что и у метода GetObjectData. Опять-таки, в основном используется аргумент info и его метод GetVaiue (key, type), который выполняет операцию, обратную к операции метода Addvaiue. По ключу key находится хранимое значение, а аргумент type позволяет привести его к нужному типу. У метода GetVaiue имеется множество типизированных версий, позволяющих не задавать тип. Так что восстановление полей name и аде можно выполнить следующими операторами:

name = infо. GetString("name"); age = infо. GetInt32("age");

Восстановление поля son, являющегося ссылочным типом, выполняется вызовом его специального конструктора:

son = new Child(info, context);

А теперь вернемся к нашему примеру со стариком, старухой и золотой рыбкой. Заменим стандартную сериализацию собственной. Для этого, оставив атрибут сериализации у класса Personage, сделаем класс наследником интерфейса ISerializabie:

[Serializable]

public class Personage: ISerializable

{…}

Добавим в наш класс специальный метод, вызываемый при сериализации — метод сохранения данных:

//Специальный метод сериализации

public void GetObjectData(Serializationlnfо info,

StreamingContext context)

{

info.AddValue("name",name); infо. AddValue("age", age);

infо.AddValue("status",status);

infо.AddValue("wealth", wealth);

info.AddValue("couplename",couple.name);

info.AddValue("coupleage", couple.age);

infо.AddValue("couplestatus",couple.status);

infо. AddValue("couplewealth", couple.wealth);

}

В трех первых строках сохраняются значимые поля объекта и тут все ясно. Но вот запомнить поле, хранящее объект couple класса Personage, напрямую не удается. Попытка рекурсивного вызова

couple.GetobjectData(info,context);

привела бы к зацикливанию, если бы раньше из-за повторяющегося ключа не возникала исключительная ситуация в момент записи поля name объекта couple. Поэтому приходится явно сохранять поля этого объекта уже с другими ключами. Понятно, что с ростом сложности структуры графа объектов задача существенно осложняется.

Добавим в наш класс специальный конструктор, вызываемый при десериализации — конструктор восстановления состояния:

//Специальный конструктор сериализации

protected Personage (Serializationlnfo info,

StreamingContext context)

{

name = infо. GetString("name"); age = infо. Getlnt32("age");

status = infо. GetString("status");

wealth = infо. GetString("wealth");

couple = new Personage(infо. GetString("couplename"),

infо. Getlnt32("coupleage"));

couple.status = infо. GetString("couplestatus");

couple.wealth = infо. GetString("couplewealth");

this.couple = couple; couple.couple = this;

}

Опять первые строки восстановления значимых полей объекта прозрачно ясны. А с полем couple приходится повозиться. Вначале создается новый объект обычным конструктором, аргументы которого читаются из сохраняемой памяти. Затем восстанавливаются значения других полей этого объекта, а затем уже происходит взаимное связывание двух объектов.

Поделиться с друзьями: