Делегаты и события
События
Если в классе объявить член-событие, то объект — представитель этого класса сможет уведомлять объекты других классов о данном событии.
Класс, содержащий объявление события, поддерживает:
- регистрацию объектов — представителей других классов, "заинтересованных" в получении уведомления о событии;
- отмену регистрации объектов, получающих уведомление о событии;
- управление списком зарегистрированных объектов и процедурой уведомления о событии.
Реализация механизма управления по событиям предполагает два этапа:
- объявление делегата,
- объявление события.
Два примера иллюстрируют технику применения событий и функциональные возможности объекта события.
Первый пример:
using System; namespace Events01 { // Делегат. delegate int ClassDLG (int key); // Классы, содержащие методы – обработчики событий. class C1 {//============================================================== public int C1f (int key) {//______________________________________________________________ Console.WriteLine("C1.C0f"); return key*2; }//______________________________________________________________ }//============================================================== class C2 {//============================================================== public int C2f (int key) {//______________________________________________________________ Console.WriteLine("C2.C0f"); return key*2; }//______________________________________________________________ }//============================================================== // Стартовый класс. class C0 {//============================================================== static event ClassDLG ev0; static event ClassDLG ev1; public static int C0F (int key) {//______________________________________________________________ Console.WriteLine("C0.C0F"); return key*2; }//______________________________________________________________ public int C0f (int key) {//______________________________________________________________ Console.WriteLine("C0.C0f"); return key*2; }//______________________________________________________________ static void Main(string[] args) {//______________________________________________________________ int i; Random rnd = new Random(); // Создается объект – представитель класса C1. C1 obj1 = new C1(); // Подписная кампания. Подписываются ВСЕ! // В событии ev0 заинтересован: // класс C0 статическим методом C0F. ev0 += new ClassDLG(C0.C0F); // В событии ev1 заинтересован: // класс C0 методом C0f, вызываемым от имени безымянного объекта, // класс C1 методом C1f, вызываемым от имени объекта obj1, // класс C2 методом C2f, вызываемым от имени безымянного объекта. ev1 += new ClassDLG((new C0()).C0f); ev1 += new ClassDLG(obj1.C1f); ev1 += new ClassDLG((new C2()).C2f); // И вот какие события... // Орел или Решка! for (i = 0; i < 100; i++) { Console.WriteLine("==={0}===",i); switch (rnd.Next(0,2)) { case 0: // Уведомляются ВСЕ заинтересованные в событии "выпадение 1". ev0(i); break; case 1: // Уведомляются ВСЕ заинтересованные в событии "выпадение 0". ev1(i); break; } } }//______________________________________________________________ }//============================================================== }Листинг 9.2.
Второй пример:
using System; namespace EVENTS { // Объявленние классов-делегатов. // Класс-делегат со стандартными сигнатурой и спецификатором // возвращаемого значения. delegate int ClassDlg_1(int key, string str); // Сигнатура делегата и возвращаемое значение могут быть любыми! delegate int ClassDlg_2(int key); // Тип второго параметра делегата для этого класса-делегата объявлен // ниже. delegate void ClassDlg_TimeInfo (object sourseKey, TimeInfoEventArgs eventKey); //=============================================================== class TimeInfoEventArgs: EventArgs {//============================================================== public TimeInfoEventArgs(int hh, int mm, int ss) { hour = hh; minute = mm; second = ss; } public readonly int hour; public readonly int minute; public readonly int second; }//============================================================== //=============================================================== // Объявляются два класса //=============================================================== class Doll_1 { public ClassDlg_1 dlg1Doll_1; // Объявлен делегат "стандартного" вида. public ClassDlg_2 dlg2Doll_1; // Объявлен делегат. public ClassDlg_TimeInfo dlgTimeInfo; // Нужна ссылка на объект - параметр делегата. // Объявлено соответствующее событие. // Событие объявляется на основе класса делегата. // Нет класса делегата – нет и события! public event ClassDlg_2 eventDoll_1; // Конструктор. В конструкторе производится настройка делегатов. public Doll_1() { dlg1Doll_1 = new ClassDlg_1(F2Doll_1); dlgTimeInfo = new ClassDlg_TimeInfo(TimeInfoDoll_1); } // Функции - члены класса, которые должны вызываться // в ответ на уведомление о событии, на которое предположительно // должен подписаться объект - представитель данного класса. //=============================================================== public int F0Doll_1(int key) { // Эта функция только сообщает... Console.WriteLine("this is F0Doll_1({0})", key); return 0; } public void F1Doll_1(int key) { // Эта функция еще и УВЕДОМЛЯЕТ подписавшийся у нее объект... // Что это за объект, функция не знает! Console.WriteLine("this is F1Doll_1({0}), fire eventDoll_1!", key); if (eventDoll_1 != null) eventDoll_1(key); } public int F2Doll_1(int key, string str) { // Эта функция сообщает и возвращает значение... Console.WriteLine("this is F2Doll_1({0},{1})", key, str); return key; } void TimeInfoDoll_1(object sourseKey,TimeInfoEventArgs eventKey) { // А эта функция вникает в самую суть события, которое несет // информацию о времени возбуждения события. Console.Write("event from {0}:", ((SC)sourseKey).ToString()); Console.WriteLine("the time is {0}:{1}:{2}",eventKey.hour.ToString(), eventKey.minute.ToString(), eventKey.second.ToString()); } } //=============================================================== //=============================================================== // Устройство второго класса проще. Нет обработчика события "времени". class Doll_2 { public ClassDlg_1 dlg1Doll_2; public ClassDlg_2 dlg2Doll_2; public event ClassDlg_2 eventDoll_2; // В конструкторе производится настройка делегатов. public Doll_2() { dlg1Doll_2 = new ClassDlg_1(F1Doll_2); dlg2Doll_2 = new ClassDlg_2(F0Doll_2); } public int F0Doll_2(int key) { // Эта функция только сообщает... Console.WriteLine("this is F0Doll_2({0})", key); return 0; } public int F1Doll_2(int key, string str) { // Эта функция сообщает и возвращает значение... Console.WriteLine("this is F1Doll_2({0},{1})", key, str); return key; } public void F2Doll_2(int key) { // Эта функция еще и УВЕДОМЛЯЕТ подписавшийся у нее объект... Console.WriteLine("this is F2Doll_2({0}), fire eventDoll_2!", key); if (eventDoll_2 != null) eventDoll_2(key); } } //=============================================================== //=============================================================== class SC // Start Class { // Объявлен класс-делегат... public static ClassDlg_2 dlgSC; // В стартовом класса объявлены два события. public static event ClassDlg_1 eventSC; public static event ClassDlg_TimeInfo timeInfoEvent; static public int FunSC(int key) { Console.WriteLine("this is FunSC({0}) from SC",key); // Первое уведомление. В теле этой функции осуществляется передача // управления методу ОБЪЕКТА, который заинтересован в данном событии. // В данном случае событием в буквальном смысле является факт передачи // управления функции FunSC. Какие объекты заинтересованы в этом // событии – сейчас сказать невозможно. Здесь только уведомляют о // событии. Подписывают заинтересованных – в другом месте! eventSC(2*key, "Hello from SC.FunSC !!!"); DateTime dt = System.DateTime.Now; // Второе уведомление. timeInfoEvent(new SC(), new TimeInfoEventArgs(dt.Hour,dt.Minute,dt.Second)); return key; } static void Main(string[] args) { // Объявили и определили два объекта. // Так вот кто интересуется событиями! Doll_1 objDoll_1 = new Doll_1(); Doll_2 objDoll_2 = new Doll_2(); // Подписали их на события. // В событии eventSC заинтересованы объекты // objDoll_1 и objDoll_2, которые здесь подписываются на // события при помощи делегатов, которые были настроены // на соответствующие функции в момент создания объекта. // Конструкторы обоих классов только и сделали, что настроили // собственные делегаты. eventSC += objDoll_1.dlg1Doll_1; eventSC += objDoll_2.dlg1Doll_2; // А в событии timeInfoEvent заинтересован лишь // объект - представитель класса Doll_1. timeInfoEvent += objDoll_1.dlgTimeInfo; // Определили делегата для функции SC.FunSC. // Это собственная статическая функция класса SC. dlgSC = new ClassDlg_2(SC.FunSC); // А теперь достраиваем делегаты, которые // не были созданы и настроены в конструкторах // соответствующих классов. objDoll_1.dlg2Doll_1 = new ClassDlg_2(objDoll_2.F0Doll_2); objDoll_2.dlg1Doll_2 = new ClassDlg_1(objDoll_1.F2Doll_1); // Подписали объекты на события. objDoll_1.eventDoll_1 +=new ClassDlg_2(objDoll_2.F0Doll_2); objDoll_2.eventDoll_2 +=new ClassDlg_2(objDoll_1.F0Doll_1); // SC будет сообщать о времени! // И неважно, откуда последует вызов функции. // Событие в классе objDoll_1 используется для вызова // статического метода класса SC. objDoll_1.eventDoll_1 +=new ClassDlg_2(SC.dlgSC); // Событие в классе objDoll_2 используется для вызова // статического метода класса SC. objDoll_2.eventDoll_2 +=new ClassDlg_2(SC.dlgSC); // Работа с делегатами. objDoll_1.dlg2Doll_1(125); objDoll_2.dlg1Doll_2(521, "Start from objDoll_2.dlg1Doll_2"); // Работа с событиями. Уведомляем заинтересованные классы и объекты! // Что-то не видно особой разницы с вызовами через делегатов. objDoll_1.F1Doll_1(125); objDoll_2.F2Doll_2(521); // Отключили часть приемников. // То есть отказались от получения некоторых событий. // И действительно. Нечего заботиться о чужом классе. objDoll_1.eventDoll_1 –= new ClassDlg_2(SC.dlgSC); objDoll_2.eventDoll_2 –= new ClassDlg_2(SC.dlgSC); // Опять работа с событиями. objDoll_1.F1Doll_1(15); objDoll_2.F2Doll_2(51); } } }Листинг 9.3.
Анонимные методы и делегаты для анонимных методов
Прежде всего, дадим определение АНОНИМНОГО МЕТОДА. В C# 2.0 это фрагмент кода, который оформляется в виде блока и располагается в теле метода или конструктора. Анонимный метод имеет характерный "заголовок", который содержит ключевое слово delegate и список параметров анонимного метода. Внутри блока, представляющего анонимный метод, действуют общепринятые правила областей видимости (scope). Анонимный метод выступает в контексте выражения инициализации делегата и при выполнении содержащего анонимный метод кода НЕ выполняется. В этот момент происходит инициализация делегата, которому все равно, на ЧТО он настроен: непосредственно на метод или на блок операторов в методе (то есть на анонимный метод).
Далее приводится пример объявления анонимного метода, настройки и активизации делегата, в результате которой и происходит выполнение кода анонимного метода:
using System; using System.Collections.Generic; using System.Text; namespace DelegatesForAnonymousMethods { delegate int DDD(char op, int key1, int key2); class DAM { public static int DAMstatic(char chKey, int iKey1, int iKey2) { // Это простой блок операторов. // При желании его можно использовать как АНОНИМНЫЙ МЕТОД. // Для этого всего лишь надо будет дописать выражение инициализации // делегата. Что и будет сделано в свое время. { int ret; ret = iKey1 % iKey2; } //return ret; // Внимание. Здесь ret не определен! Console.WriteLine("DAMstatic: {0},{1},{2}", chKey, iKey1, iKey2); return 0; } // Делегат как параметр! // Функция модифицирует значение делегата, передаваемого как ссылка // на ссылку, добавляя код еще одного анонимного метода, // и выполняет при этом свою собственную работу. public int xDAM(ref DDD dKey) { Console.WriteLine("Hello from xDAM!"); dKey += delegate(char op, int key1, int key2) { Console.WriteLine("this code was from xDAM"); return ((string)(op.ToString()+key1.ToString()+key2.ToString())).GetHashCode(); }; int x = 0; while (true) { x++; if (x == 100) break; } return 0; } // И опять делегат как параметр... // Выполнение набранного кода. public void goDAM(DDD dKey) { int ret; Console.WriteLine("this is goDAM!"); ret = dKey('+', 5, 5); Console.WriteLine(ret); } } class Program { static void Main(string[] args) { DDD d0; int x; x = 0; // Это АНОНИМНЫЙ МЕТОД. // Объявление анонимного метода - составная часть выражения инициализации // делегата. В момент инициализации сам код не выполняется! d0 = delegate(char op, int key1, int key2) { int res; switch (op) { case '+': res = key1 + key2; break; case '-': res = key1 - key2; break; case '*': res = key1 * key2; break; case ':': try { res = key1 / key2; } catch { Console.Write("That was a wrong number! "); res = int.MaxValue; } break; default: Console.Write("That was illegal symbol of operation! "); res = int.MinValue; break; } Console.WriteLine(">>>{0}<<<", res); return res; };// Конец кода для инициализации делегата (конец анонимного метода). x++; // Делегат настраивается на фрагмент кода. // И вот теперь этот код выполняется... x = d0('*', 125, 7); Console.WriteLine(x); // Даже будучи настроенным на анонимный метод, делегат остается делегатом! // Вот он настраивается на полноценную статическую функцию... d0 += new DDD(DAM.DAMstatic); x = d0(':', 125, 25); Console.WriteLine(x); Console.WriteLine("___________________________________________"); // А вот сам выступает в качестве параметра // и передается функции, в которой делегату // передается ссылка еще на один анонимный метод. DAM dam = new DAM(); dam.xDAM(ref d0); x = d0('-', 125, 5); Console.WriteLine(x); Console.WriteLine("___________________________________________"); dam.goDAM(d0); Console.WriteLine("____________________________________________"); } } }Листинг 9.4.
События и делегаты. Различия
Так в чем же разница между событиями и делегатами в .NET?
В последнем примере предыдущего раздела при объявлении события очевидно его строгое соответствие определенному делегату:
public static event System.EventHandler xEvent;
System.EventHandler – это ТИП ДЕЛЕГАТА! Оператор, который обеспечивает процедуру "подписания на уведомление", полностью соответствует оператору модификации многоадресного делегата. Аналогичным образом дело обстоит и с процедурой "отказа от уведомления":
BaseClass.xEvent += new System.EventHandler(this.MyFun); BaseClass.xEvent –= new System.EventHandler(xxx.MyFun);
И это действительно так. За операторными функциями += и –= скрываются методы классов-делегатов (в том числе и класса-делегата System.EventHandler ).
Более того. Если в последнем примере в объявлении события ВЫКИНУТЬ ключевое слово event –
public static event System.EventHandler xEvent;
и заменить его на
public static System.EventHandler xEvent;
System.EventHandler (это класс-делегат!), то ничего не произойдет. Вернее, ВСЕ будет происходить, как и раньше! Вместо пары СОБЫТИЕ-ДЕЛЕГАТ будет работать пара ДЕЛЕГАТ-ДЕЛЕГАТ.
Таким образом, функционально событие является всего лишь разновидностью класса-делегата, главной задачей которого является обеспечение строгой привязки делегата к соответствующему событию.
Модификатор event вносит лишь незначительные синтаксические нюансы в использование этого МОДИФИЦИРОВАННОГО делегата — чтобы хоть как-нибудь различать возбудителя и получателя события.