Российский Государственный Технологический Университет им. К.Э. Циолковского
Опубликован: 02.03.2007 | Доступ: свободный | Студентов: 5636 / 790 | Оценка: 3.96 / 3.45 | Длительность: 27:04:00
ISBN: 978-5-9556-0086-4
Лекция 9:

Делегаты и события

< Лекция 8 || Лекция 9: 12 || Лекция 10 >

События

Если в классе объявить член-событие, то объект — представитель этого класса сможет уведомлять объекты других классов о данном событии.

Класс, содержащий объявление события, поддерживает:

  • регистрацию объектов — представителей других классов, "заинтересованных" в получении уведомления о событии;
  • отмену регистрации объектов, получающих уведомление о событии;
  • управление списком зарегистрированных объектов и процедурой уведомления о событии.

Реализация механизма управления по событиям предполагает два этапа:

  • объявление делегата,
  • объявление события.

Два примера иллюстрируют технику применения событий и функциональные возможности объекта события.

Первый пример:

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 вносит лишь незначительные синтаксические нюансы в использование этого МОДИФИЦИРОВАННОГО делегата — чтобы хоть как-нибудь различать возбудителя и получателя события.

< Лекция 8 || Лекция 9: 12 || Лекция 10 >
kewezok kewezok
kewezok kewezok
Елена Шляхт
Елена Шляхт
Объясните плиз в чем отличие а++ от ++а
Почему результат разный?
int a=0, b=0;
Console.WriteLine(a++); //0
Console.WriteLine(++b); //1
a++;
++b;
Console.WriteLine(a); //2
Console.WriteLine(b); //2