Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:
persons.PrintPersons ;
Console.WriteLine (" Сортировка по зарплате и имени: ");
persons.SimpleSortPerson(Person.SortBySalaryName);
persons.PrintPersons ;
}//SortPersons
Результаты работы сортировки данных изображены на рис. 20.5.
Рис. 20.5. Сортировка данных
Операции над делегатами. Класс Delegate
Давайте просуммируем то, что уже известно о функциональном типе данных. Ключевое слово delegate позволяет задать определение функционального типа (класса),
Вместе с тем, объявление функционального типа не укладывается в синтаксис, привычный для С#. Хотелось бы писать, как принято:
Delegate FType = new Delegate(<определение типа>)
Но так объявлять переменные этого класса нельзя, и стоит понять, почему. Есть ли вообще класс Delegate? Ответ положителен — есть такой класс. При определении функционального типа, например:
public delegate int FType(int X);
переменная FType принадлежит классу Delegate. Почему же ее нельзя объявить привычным образом? Дело не только в синтаксических особенностях этого класса. Дело в том, что класс Delegate является абстрактным классом. Вот его объявление:
public abstract class Delegate: ICloneable, ISerializable
Для абстрактных классов реализация не определена, и это означает, что нельзя создавать экземпляры класса. Класс Delegate служит базовым классом для классов — наследников. Но создавать наследников могут только компиляторы и системные программы — этого нельзя сделать в программе на С#. Именно поэтому введено ключевое слово delegate, которое косвенно позволяет работать с классом Delegate, создавая уже не абстрактный, а реальный класс. Заметьте, при этом все динамические и статические методы класса Delegate становятся доступными программисту.
Трудно, кажется, придумать, что можно делать с делегатами. Однако, у них есть одно замечательное свойство — их можно комбинировать. Представьте себе, что существует список работ, которые нужно выполнять, в зависимости от обстоятельств, в разных комбинациях. Если функции, выполняющие отдельные работы, принадлежат одному классу, то для решения задачи можно использовать делегатов и использовать технику их комбинирования. Замечу, что возможность комбинирования делегатов появилась, в первую очередь, для поддержания работы с событиями. Когда возникает некоторое событие, то сообщение о нем посылается разным объектам, каждый из которых по-своему обрабатывает событие. Реализуется эта возможность на основе комбинирования делегатов.
В чем суть комбинирования делегатов? Она прозрачна. К экземпляру делегату разрешается поочередно присоединять другие экземпляры делегата того же типа. Поскольку каждый экземпляр хранит ссылку на функцию, то в результате создается список ссылок. Этот список называется списком вызовов (invocation list). Когда вызывается экземпляр, имеющий список вызова, то поочередно, в порядке присоединения, начинают вызываться и выполняться функции, заданные ссылками. Так один вызов порождает выполнение списка работ.
Понятно, что, если есть операция присоединения делегатов, то должна быть и обратная операция, позволяющая удалять делегатов из списка.
Рассмотрим основные методы и свойства класса Delegate. Начнем с двух статических методов — Combine и Remove.
Первый из них присоединяет экземпляры делегата к списку, второй — удаляет из списка. Оба метода имеют похожий синтаксис:Combine(del1, del2)
Remove(del1, del2)
Аргументы del1 и del2 должны быть одного функционального класса. При добавлении del2 в список, в котором del2 уже присутствует, будет добавлен второй экземпляр. При попытке удаления del2 из списка, в котором del2 нет, Remove благополучно завершит работу, не выдавая сообщения об ошибке.
Класс Delegate относится к неизменяемым классам, поэтому оба метода возвращают ссылку на нового делегата. Возвращаемая ссылка принадлежит родительскому классу Delegate, поэтому ее необходимо явно преобразовать к нужному типу, которому принадлежат del1 и del2. Обычное использование этих методов имеет вид:
del1 = (<type>) Combine(del1, del2); del1 = (<type>) Remove(del1, del2);
Метод GetInvocationList является динамическим методом класса — он возвращает список вызовов экземпляра, вызвавшего метод. Затем можно устроить цикл foreach, поочередно получая элементы списка. Чуть позже появится пример, поясняющий необходимость подобной работы со списком.
Два динамических свойства Method и Target полезны для получения подробных сведений о делегате. Чаще всего они используются в процессе отражения, когда делегат поступает извне и необходима метаинформация, поставляемая с делегатом. Свойство Method возвращает объект класса MethodInfo из пространства имен Reflection. Свойство Target возвращает информацию об объекте, вызвавшем делегата, в тех случаях, когда делегат инициируется не статическим методом класса, а динамическим, связанным с вызвавшим его объектом.
У класса Delegate, помимо методов, наследуемых от класса object, есть еще несколько методов, но мы на них останавливаться не будем, они используются не столь часто.
Операции "+" и "-"
Наряду с методами, над делегатами определены и две операции: "+" и "-", которые являются более простой формой записи добавления делегатов в список вызовов и удаления из списка. Операции заменяют собой методы Combine и Remove. Выше написанные присваивания объекту dell с помощью этих операций могут быть переписаны в виде:
del1 +=del2;
del1 — =del2;
Как видите, запись становится проще, исчезает необходимость в задании явного приведения к типу. Ограничения на del1 и del2, естественно, остаются те же, что и для методов Combine и Remove.
Пример "Комбинирование делегатов"
Рассмотрим следующую ситуацию. Пусть есть городские службы: милиция, скорая помощь, пожарные. Каждая из служб по-своему реагируют на события, происходящие в городе. Построим примитивную модель жизни города, в которой случаются события и сообщения о них посылаются службам. В последующей лекции эта модель будет развита. Сейчас она носит формальный характер, демонстрируя, главным образом, работу с делегатами, заодно поясняя ситуации, в которых разумно комбинирование делегатов.