Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
В эту форму входят два аксессора событий: add и remove. Аксессор add вызывается,когда обработчик событий добавляется в цепочку событий с помощью оператора +=.В то же время аксессор remove вызывается, когда обработчик событий удаляется изцепочки событий с помощью оператора -=.
Когда вызывается аксессор add или remove, он принимает в качестве параметрадобавляемый или удаляемый обработчик. Как и в других разновидностях аксессоров,этот неявный параметр называется value. Реализовав аксессоры add или remove,можно организовать специальную схему хранения обработчиков событий. Например,обработчики событий можно хранить в массиве, стеке или очереди.
Ниже приведен пример программы, демонстрирующей аксессорную форму события. В ней для хранения обработчиков событий используется массив. Этот массивсостоит всего из трех элементов, поэтому в цепочке событий можно хранить одновременно только три обработчика.// Создать специальные средства для управления списками// вызова обработчиков событий.using System;// Объявить тип делегата для события.delegate void MyEventHandler();// Объявить класс для хранения максимум трех событий.class MyEvent { MyEventHandler[] evnt = new MyEventHandler[3]; public event MyEventHandler SomeEvent { // Добавить событие в список. add { int i; for(i=0; i < 3; i++) if(evnt[i] == null) { evnt[i] = value; break; } if (i == 3) Console.WriteLine("Список событий заполнен."); } // Удалить событие из списка. remove { int i; for(i=0; i < 3; i++) if(evnt[i] == value) { evnt[i] = null; break; } if (i == 3) Console.WriteLine("Обработчик событий не найден."); } } // Этот метод вызывается для запуска событий. public void OnSomeEvent() { for(int i=0; i < 3; i++) if(evnt[i] != null) evnt[i](); }}// Создать ряд классов, использующих делегат MyEventHandler.class W { public void Whandler() { Console.WriteLine("Событие получено объектом W"); }}class X { public void Xhandler() { Console.WriteLine("Событие получено объектом X"); }}class Y { public void Yhandler() { Console.WriteLine("Событие получено объектом Y"); }}class Z { public void Zhandler() { Console.WriteLine("Событие получено объектом Z"); }}class EventDemo5 { static void Main() { MyEvent evt = new MyEvent(); W wOb = new W(); X xOb = new X(); Y yOb = new Y(); Z zOb = new Z(); // Добавить обработчики в цепочку событий. Console.WriteLine("Добавление событий. "); evt.SomeEvent += wOb.Whandler; evt.SomeEvent += xOb.Xhandler; evt.SomeEvent += yOb.Yhandler; // Сохранить нельзя - список заполнен. evt.SomeEvent += zOb.Zhandler; Console.WriteLine(); // Запустить события. evt.OnSomeEvent(); Console.WriteLine(); // Удалить обработчик. Console.WriteLine("Удаление обработчика xOb.Xhandler."); evt.SomeEvent -= xOb.Xhandler; evt.OnSomeEvent(); Console.WriteLine(); // Попробовать удалить обработчик еще раз. Console.WriteLine("Попытка удалить обработчик " + "xOb.Xhandler еще раз."); evt.SomeEvent -= xOb.Xhandler; evt.OnSomeEvent(); Console.WriteLine(); // А теперь добавить обработчик Zhandler. Console.WriteLine("Добавление обработчика zOb.Zhandler."); evt.SomeEvent += zOb.Zhandler; evt.OnSomeEvent(); }}
Ниже приведен результат выполнения этой программы:Добавление событий.Список событий заполнен.Событие получено объектом WСобытие получено объектом XСобытие получено объектом YУдаление обработчика xOb.Xhandler.Событие получено объектом WСобытие получено объектом YПопытка удалить обработчик xOb.Xhandler еще раз.Обработчик событий не найден.Событие получено объектом WСобытие получено объектом YДобавление обработчика zOb.Zhandler.Событие получено объектом WСобытие получено объектом XСобытие получено объектом Y
Рассмотрим данную программу более подробно. Сначала в ней определяется делегат обработчиков событий MyEventHandler. Затем объявляется класс MyEvent. В самом его начале определяется массив обработчиков событий evnt, состоящий из трехэлементов.MyEventHandler[] evnt = new MyEventHandler[3];
Этот массив служит для хранения обработчиков событий, добавляемых в цепочкусобытий. По умолчанию элементы массива evnt инициализируются пустым значением (null).
Далее объявляется событие SomeEvent. В этом объявлении используется приведенная ниже аксессорная форма оператора event.public event MyEventHandler SomeEvent { // Добавить событие в список. add { int i; for(i=0; i < 3; i++) if(evnt[i] == null) { evnt[i] = value; break; } if (i == 3) Console.WriteLine("Список событий заполнен."); } // Удалить событие из списка. remove { int i; for(i=0; i < 3; i++) if(evnt[i] == value) { evnt[i] = null; break; } if (i == 3) Console.WriteLine("Обработчик событий не найден."); }}
Когда в цепочку событий добавляется обработчик событий, вызывается аксессорadd, и в первом неиспользуемом (т.е. пустом) элементе массива evnt запоминаетсяссылка на этот обработчик, содержащаяся в неявно задаваемом параметре value. Еслив массиве отсутствуют свободные элементы, то выдается сообщение об ошибке. (Разумеется, в реальном коде при переполнении списка лучше сгенерировать соответствующее исключение.) Массив evnt состоит всего из трех элементов, поэтому в нем можносохранить только три обработчика событий. Когда же обработчик событий удаляетсяиз цепочки событий, то вызывается аксессор remove и в массиве evnt осуществляетсяпоиск ссылки на этот обработчик, передаваемой в качестве параметра value. Еслиссылка найдена, то соответствующему элементу массива присваивается пустое значение (null), а значит, обработчик удаляется из цепочки событий.
При запуске события вызывается метод OnSomeEvent(). В этом методе происходитциклическое обращение к элементам массива evnt для вызова по очереди каждого обработчика событий.
Как демонстрирует рассматриваемый здесь пример программы, механизм храненияобработчиков событий нетрудно реализовать, если в этом есть потребность. Но для большинства приложений более подходящим оказывается используемый по умолчанию механизм хранения обработчиков событий, который обеспечивает форма оператора eventбез аксессоров. Тем не менее аксессорная форма оператора event используется в особыхслучаях. Так, если обработчики событий необходимо выполнять в программе в порядкеих приоритетности, а не в том порядке, в каком они вводятся в цепочку событий, то для иххранения можно воспользоваться очередью по приоритету.
ПРИМЕЧАНИЕВ многопоточных приложениях обычно приходится синхронизировать доступ к аксессорам событий. Подробнее о многопоточном программировании речь пойдет в главе 23.Разнообразные возможности событий
События могут быть определены и в интерфейсах. При этом события должныпредоставляться классами, реализующими интерфейсы. События могут быть такжеопределены как абстрактные (abstract). В этом случае конкретное событие должнобыть реализовано в производном классе. Но аксессорные формы событий не могутбыть абстрактными. Кроме того, событие может быть определено как герметичное(sealed). И наконец, событие может быть виртуальным, т.е. его можно переопределить в производном классе.Применение анонимных методов и лямбда-выражений вместе с событиями
Анонимные методы и лямбда-выражения особенно удобны для работы с событиями, поскольку обработчик событий зачастую вызывается только в коде, реализующеммеханизм обработки событий. Это означает, что создавать автономный метод, как правило, нет никаких причин. А с помощью лямбда-выражений или анонимных методовможно существенно упростить код обработки событий.
Как упоминалось выше, лямбда-выражениям теперь отдается большее предпочтение по сравнению с анонимными методами, поэтому начнем именно с них. Нижеприведен пример программы, в которой лямбда-выражение используется в качествеобработчика событий.// Использовать лямбда-выражение в качестве обработчика событий.using System;// Объявить тип делегата для события.delegate void MyEventHandler (int n);// Объявить класс, содержащий событие.class MyEvent { public event MyEventHandler SomeEvent; // Этот метод вызывается для запуска события. public void OnSomeEvent(int n) { if (SomeEvent != null) SomeEvent(n); }}class LambdaEventDemo { static void Main() { MyEvent evt = new MyEvent(); // Использовать лямбда-выражение в качестве обработчика событий. evt.SomeEvent += (n) => Console.WriteLine("Событие получено. Значение равно " + n); // Запустить событие. evt.OnSomeEvent(1); evt.OnSomeEvent(2); }}