Добавить в цитаты Настройки чтения

Страница 321 из 371

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

Кроме введения конструктора класса и метода GetObjectData, никаких других изменений в проекте не понадобилось — ни в методах класса, ни на стороне клиента. Внешне проект работал совершенно идентично ситуации, когда не вводилось наследование интерфейса сериализации. Но с внутренних позиций изменения произошли: методы форматеров Serialize и Deserialize в процессе своей работы теперь вызывали созданный нами метод и конструктор класса. Небольшие изменения произошли и в файлах, хранящих данные.

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

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

Таблица 19.1. Размеры файлов при различных случаях сериализации

Формат ∙ Сериализация ∙ Размер файла

Бинарный поток ∙ Стандартная ∙ 355 байтов

Бинарный поток ∙ Управляемая ∙ 355 байтов

XML-документ ∙ Стандартная ∙ 1,14 Кб.

XML-документ ∙ Управляемая ∙ 974 байта

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

20. Функциональный тип в С#. Делегаты

Новое слово для старого понятия. Функциональный тип. Функции высших порядков. Вычисление интеграла и сортировка. Два способа взаимодействия частей при построении сложных систем. Функции обратного вызова. Наследование и функциональные типы. Сравнение двух подходов. Класс Delegate. Методы и свойства класса. Операции над делегатами. Комбинирование делегатов. Список вызовов.

Как определяется функциональный тип и как появляются его экземпляры

Слово делегат (delegate) используется в C# для обозначения хорошо известного понятия. Делегат задает определение функционального типа (класса) данных. Экземплярами класса являются функции. Описание делегата в языке C# представляет собой описание еще одного частного случая класса.

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