Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:
Кроме введения конструктора класса и метода GetObjectData, никаких других изменений в проекте не понадобилось — ни в методах класса, ни на стороне клиента. Внешне проект работал совершенно идентично ситуации, когда не вводилось наследование интерфейса сериализации. Но с внутренних позиций изменения произошли: методы форматеров Serialize и Deserialize в процессе своей работы теперь вызывали созданный нами метод и конструктор класса. Небольшие изменения произошли и в файлах, хранящих данные.
Мораль: должны быть веские основания
Когда в нашем примере вводилось собственное управление сериализацией, то не ставилась цель минимизации объема хранимых данных, в обоих случаях сохранялись одни и те же данные. Тем не менее представляет интерес взглянуть на таблицу, хранящую объемы создаваемых файлов.
Таблица 19.1. Размеры файлов при различных случаях сериализации
Формат • Сериализация • Размер файла
Бинарный поток • Стандартная • 355 байтов
Бинарный поток • Управляемая • 355 байтов
XML-документ • Стандартная • 1,14 Кб.
XML-документ • Управляемая • 974 байта
Преимуществами XML-документа являются его читабельность и хорошо развитые средства разбора, но зато бинарное представление выигрывает в объеме и скорости передачи тех же данных.
20. Функциональный тип в С#. Делегаты
Новое слово для старого понятия. Функциональный тип. Функции высших порядков. Вычисление интеграла и сортировка. Два способа взаимодействия частей при построении сложных систем. Функции обратного вызова. Наследование и функциональные типы. Сравнение двух подходов. Класс Delegate. Методы и свойства класса. Операции над делегатами. Комбинирование делегатов. Список вызовов.
Как определяется функциональный тип и как появляются его экземпляры
Слово делегат (delegate) используется в C# для обозначения хорошо известного понятия. Делегат задает определение функционального типа (класса) данных. Экземплярами класса являются функции. Описание делегата в языке C# представляет собой описание еще одного частного случая класса.
Каждый делегат описывает множество функций с заданной сигнатурой. Каждая функция (метод), сигнатура которого совпадает с сигнатурой делегата, может рассматриваться как экземпляр класса, заданного делегатом. Синтаксис объявления делегата имеет следующий вид:
[<спецификатор доступа>] delegate <тип результата > <имя класса> (<список аргументов>);
Этим объявлением класса задается функциональный тип — множество функций с заданной сигнатурой, у которых аргументы определяются списком, заданным в объявлении делегата, и тип возвращаемого значения определяется типом результата делегата.
Спецификатор доступа может быть, как обычно, опущен. Где следует размещать объявление делегата? Как и у всякого класса, есть две возможности:
• непосредственно в пространстве имен, наряду с объявлениями других классов, структур, интерфейсов;
• внутри другого класса, наряду с объявлениями методов и свойств. Такое объявление рассматривается
как объявление вложенного класса.Так же, как и интерфейсы С#, делегаты не задают реализации. Фактически между некоторыми классами и делегатом заключается контракт на реализацию делегата. Классы, согласные с контрактом, должны объявить у себя статические или динамические функции, сигнатура которых совпадает с сигнатурой делегата. Если контракт выполняется, то можно создать экземпляры делегата, присвоив им в качестве значений функции, удовлетворяющие контракту. Заметьте, контракт является жестким: не допускается ситуация, при которой у делегата тип параметра — object, а у экземпляра соответствующий параметр имеет тип, согласованный с object, например, int.
Начнем примеры этой лекции с объявления трех делегатов. Поместив два из них в пространство имен, третий вложим непосредственно в создаваемый нами класс:
namespace Delegates {
//объявление классов — делегатов
delegate void Proc(ref int x);
delegate void MesToPers(string s);
class OwnDel
{
public delegate int Fun1(int x);
int Plus1(int x){return(x+100);}//Plus1
int Minus1(int x){return(x-100);}//Minus1
void Plus(ref int x){x+= 100;}
void Minus(ref int x){x-=100;}
//поля класса
public Proc p1;
public Fun1 f1;
char sign;
//конструктор
public OwnDel(char sign)
{
this.sign = sign;
if (sign == '+')
{p1 = new Proc(Plus);f1 = new Fun1(Plus1);}
else
{p1 = new Proc(Minus);f1 = new Fun1(Minus1);}
}
}//class OwnDel
Прокомментирую этот текст.
• Первым делом объявлены три функциональных класса — три делегата: Proc, MesToPers, Fun1. Каждый из них описывает множество функций фиксированной сигнатуры.
• В классе OwnDel описаны четыре метода: Plus, Minus, Plus1, Minus1, сигнатуры которых соответствуют сигнатурам, задаваемых классами рrос и Fun1.
• Поля p1 и f1 класса OwnDel являются экземплярами классов рrос и Fun1.
• В конструкторе класса поля p1 и f1 связываются с конкретными методами Plus или Minus, Plus1 или Minus1. Связывание с той или иной функцией в данном случае определяется значением поля sign.
Заметьте, экземпляры делегатов можно рассматривать как ссылки (указатели на функции), а методы тех или иных классов с соответствующей сигнатурой можно рассматривать как объекты, хранимые в динамической памяти. В определенный момент происходит связывание ссылки и объекта (в этой роли выступают не обычные объекты, имеющие поля, а методы, задающие код). Взгляд на делегата как на указатель функции характерен для программистов, привыкших к C++.
Приведу теперь процедуру, тестирующую работу созданного класса: