На этом шаге мы рассмотрим различие между событием и экземпляром делегата.
На первый взгляд может показаться, что между событием и экземпляром делегата принципиальной разницы нет. Правда мы не можем "вызвать" (или сгенерировать) событие вне пределов класса, а при операциях с событием можем использовать только операторы += и -=: во всем остальном события и экземпляры делегатов действительно похожи. Но разница между ними все же имеется, причем принципиальная. Событие от экземпляра делегата отличается примерно так же, как свойство отличается от текстового поля (роль текстового поля в данном случае играет ссылка на экземпляр делегата). За событием "спрятана" ссылка на экземпляр делегата и два метода-аксессора, которые отвечают за добавление методов в список обработчиков события и за удаление методов из списка обработчиков события. Если в классе объявлено событие, то все это "богатство" (ссылка на экземпляр делегата и методы-аксессоры) создается автоматически, без нашего участия. Но мы можем реализовать все самостоятельно, в явном виде.
Событие можно объявлять с явным описанием аксессоров. Аксессор, вызываемый при добавлении (с помощью оператора +=) метода в список обработчиков события, называется add. Аксессор, который вызывается при удалении (с помощью оператора -=) ссылки на метод из списка обработчиков события, называется remove. Ключевое слово value обозначает значение (ссылка на метод или экземпляр делегата), находящееся в правой части выражения с оператором += или -=. Общий шаблон объявления события в таком случае имеет следующий вид:
public event делегат событие { add { // Команды add-аксессора } remove { // Команды remove-аксессора } }
Фактически событие в этом случае описывается как свойство, только вместо get-аксессора и set-аксессора используются add-аксессор и remove-аксессор. Преимущество такого способа описания события в том, что мы можем изменить используемый по умолчанию алгоритм добавления (удаления) методов в список (из списка) обработчиков события. Как иллюстрацию к сказанному рассмотрим аналог программы из предыдущего шага, но только на этот раз для реализации события мы воспользуемся явным описанием аксессоров, а для записи списка обработчиков события будем использовать закрытое поле, являющееся ссылкой на экземпляр делегата. Интересующий нас программный код представлен ниже.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr185_1 { // Объявление делегата: delegate void MyDelegate(string txt); // Класс с событием: class MyClass { // Закрытое поле (ссылка на экземпляр делегата) // для реализации списка обработчиков события: private MyDelegate myevent; // Объявление события (с описанием аксессоров): public event MyDelegate MyEvent { // Аксессор для добавления метода в список // обработчиков события: add { // Добавление ссылки на метод в список вызовов // экземпляра делегата: myevent += value; } // Аксессор для удаления метода из списка // обработчиков события: remove { // Удаление ссылки на метод из списка вызовов // экземпляра делегата: myevent -= value; } } // Метод для генерирования события: public void RaiseMyEvent(string txt) { // Если для события предусмотрены обработчики: if (myevent != null) { // Генерирование события // (вызов экземпляра делегата): myevent(txt); } } } // Класс: class Alpha { public string name; public Alpha(string txt) { name = txt; } public void show(string msg) { Console.WriteLine("Объект " + name + ":"); Console.WriteLine(msg); } } // Класс с главным методом: class Program { static void Main() { MyClass obj = new MyClass(); Alpha A = new Alpha("A"); Alpha B = new Alpha("B"); obj.RaiseMyEvent("1-e событие"); obj.MyEvent += A.show; obj.RaiseMyEvent("2-e событие"); Console.WriteLine(); obj.MyEvent += B.show; obj.RaiseMyEvent("3-е событие"); Console.WriteLine(); obj.MyEvent -= A.show; obj.RaiseMyEvent("4-e событие"); Console.WriteLine(); obj.MyEvent -= A.show; obj.MyEvent -= B.show; obj.RaiseMyEvent("5-e событие"); MyDelegate md = A.show; md += B.show; obj.MyEvent += md; obj.RaiseMyEvent("6-е событие"); // Задержка: Console.ReadLine(); } } }
Результат выполнения программы точно такой же, как и на предыдущем шаге.
Рис.1. Результат выполнения программы
В этом примере, по сравнению с программой из предыдущего шага, изменилось только описание класса MyClass. Теперь здесь появилось закрытое поле myevent типа MyDelegate. Это ссылка на экземпляр делегата, через который реализуется список обработчиков для события MyEvent. Событие описывается таким блоком кода:
public event MyDelegate MyEvent { // Аксессор для добавления метода в список // обработчиков события: add { // Добавление ссылки на метод в список вызовов // экземпляра делегата: myevent += value; } // Аксессор для удаления метода из списка // обработчиков события: remove { // Удаление ссылки на метод из списка вызовов // экземпляра делегата: myevent -= value; } }
Код add-аксессора выполняется при добавлении новой ссылки в список обработчиков для события MyEvent с помощью оператора +=. В этом случае командой
myevent += value;
myevent -= value;
Несколько изменился код метода RaiseMyEvent(), предназначенного для генерирования события. Теперь в теле метода с помощью условной конструкции проверяется условие myevent!=null (поле myevent содержит ссылку на экземпляр делегата), и если условие истинно, то с помощью вызова соответствующего экземпляра делегата происходит "генерирование события".
Все остальное происходит так же, как и в программе из предыдущего шага.
На следующем шаге мы резюмируем все ранее сказанное по данной теме на предыдущих шагах.