Интернет-журнал "Домашняя лаборатория", 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-документ, который мог быть создан в любом из приложений. Вот как выглядит этот документ,
Рис. 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 приходится повозиться. Вначале создается новый объект обычным конструктором, аргументы которого читаются из сохраняемой памяти. Затем восстанавливаются значения других полей этого объекта, а затем уже происходит взаимное связывание двух объектов.