Введение в программирование на C# 2.0

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

Показывать лекцию целиком

Класс, структура, интерфейс, перечисление, делегат – это все разнообразные категории классов. Каждая категория имеет свои особенности объявления, свое назначение и строго определенную область применения.

Делегаты

Делегат – это класс.

Объявление класса делегата начинается ключевым словом delegate и выглядит следующим образом:

ОбъявлениеКлассаДелегата ::=
          [СпецификаторДоступа]
           delegate
              СпецификаторВозвращаемогоЗначения
              	ИмяКлассаДелегата
                     (СписокПараметров);

При этом

СпецификаторВозвращаемогоЗначения ::= ИмяТипа
ИмяКлассаДелегата ::= Идентификатор

а синтаксис элемента СписокПараметров аналогичен списку параметров функции. Но сначала – примеры объявления классов делегатов:

delegate int ClassDelegate(int key);
delegate void XXX(int intKey, float fKey);

Подобие объявления класса-делегата и заголовка функции не случайно.

Класс-делегат способен порождать объекты. При этом назначение объекта — представителя класса-делегата заключается в представлении методов (функций-членов) РАЗЛИЧНЫХ классов. В частности, тех которые оказываются видимыми из пространства имен, содержащих объявление данного класса-делегата. Объект этого класса способен представлять ЛЮБЫЕ (статические и нестатические) функции — члены классов, лишь бы спецификация их возвращаемого значения и список параметров соответствовали бы характеристикам данного класса-делегата.

Любой класс-делегат наследует System.MulticastDelegate. Это обстоятельство определяет многоадресность делегатов: в ходе выполнения приложения объект-делегат способен запоминать ссылки на произвольное количество функций независимо от их статичности или нестатичности и принадлежности классам. Многоадресность обеспечивается внутренним списком, в который заносятся ссылки на функции, соответствующие заданной сигнатуре и спецификации возвращаемого значения.

Свойства и методы классов-делегатов
Свойства и методыНазначение
Method Свойство. Возвращает имя метода, на который указывает делегат
Target Свойство. Возвращает имя класса, если делегат указывает на нестатический метод класса. Возвращает значение типа null, если делегат указывает на статический метод
Combine(), operator+(), operator+=(), operator–(), operator–=() Функция и операторные функции. Обеспечивают реализацию многоадресного делегата. Работа с операторными функциями смотрится как прибавление и вычитание ДЕЛЕГАТОВ. Арифметика делегатов
GetInvocationList() Основываясь на внутреннем списке ссылок на функции, строится соответствующий массив описателей типов функций. Попытка применения метода к пустому делегату приводит к возникновению исключения
object DynamicInvoke (object[] args) В соответствии со списком ссылок обеспечивается выполнение функций, на которые был настроен делегат
static Remove() Статический метод, обеспечивающий удаление элементов внутреннего списка ссылок на функции

Пример объявления и применения делегата представлен ниже.

using System;
namespace Delegates_1
{
// Класс-делегат. Его объявление соответствует типу функции,
// возвращающей значение int с одним параметром типа int. 
delegate int xDelegate(int key);
	
//===========================================
class ASD
{
// Делегат как член класса.
public xDelegate d;

// А вот свойство, которое возвращает ссылку на делегат.
public xDelegate D
{
get
{
return d;
}
} 
}

//========================================================
// Класс, содержащий функцию-член, на которую может
// быть настроен делегат - представитель класса xDelegate. 
//========================================================
class Q
{

public int QF(int key)
{
Console.WriteLine("int QF({0})", key);
return key; 
}

// А вот функция, в которой делегат используется как параметр! 
public void QQQ(int key, xDelegate par)
{
Console.WriteLine("Делегат как параметр!");

// И этот делегат используется по назначению – обеспечивает вызов
// НЕКОТОРОЙ функции. Какой функции? А неизвестно какой! Той, 
// на которую был настроен делегат перед тем, как его ссылка была передана 
// в функцию QQQ. Здесь не интересуются тем, что за функция пришла. 
// Здесь запускается ЛЮБАЯ функция, на которую настраивался делегат.
par(key);	
}
}

//===========================================
// Стартовый класс. Также содержит пару пригодных для настройки
// делегата функций.
class StartClass
{

// Одна статическая...
public static int StartClassF0(int key)
{
Console.WriteLine("int StartClassF0({0})", key);
return key; 	
}

// Вторая нестатическая...
public int StartClassF1(int key)
{
Console.WriteLine("int StartClassF1({0})", key);
return key; 	
}

// Процесс пошел!
static void Main(string[] args)
{//===============================================================

// Ссылка на делегат.
xDelegate localDelegate;
int i, n;

// Объект – представитель класса StartClass.
StartClass sc = new StartClass();

// Объект – представитель класса Q.
Q q = new Q();

// Объект – представитель класса ASD.
ASD asd = new ASD();

// Статическая функция – член класса – в списке делегата.
// Поскольку делегат настраивается непосредственно в 
// классе, которому принадлежит данная статическая функция,
// здесь обходимся без дополнительной спецификации имени
// функции.

asd.d = new xDelegate(StartClassF0);
// Вот показали имя метода, на который настроен делегат. 
Console.WriteLine(asd.d.Method.ToString());

// Попытались показать имя класса – хозяина метода, на который
// настроили делегат. Ничего не получится, поскольку метод – 
// статический.
if (asd.d.Target != null) Console.WriteLine(asd.d.Target);

// неСтатическая функция – член класса – в списке делегата.
// добавляется к списку функций делегата посредством
// операторной функции +=.

asd.d += new xDelegate(sc.StartClassF1);
Console.WriteLine(asd.d.Method.ToString());
if (asd.d.Target != null) Console.WriteLine(asd.d.Target);

// Делегат также включил в список функцию – член класса Q.

asd.d += new xDelegate(q.QF);
Console.WriteLine(asd.d.Method.ToString());
if (asd.d.Target != null) Console.WriteLine(asd.d.Target);


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

// Либо так.
asd.d(125);

// Либо так. Параметр
// при этом пришлось упаковать в одномерный массив типа object
// длиной в 1.
asd.d.DynamicInvoke(new object[]{0});

// Также есть возможность удаления функции из списка делегата.
// Для этого воспользуемся локальным делегатом. 
localDelegate = new xDelegate(q.QF);
asd.d –= localDelegate;

asd.d(725);
// А теперь опять добавим функцию в список делегата.
asd.d += localDelegate;

asd.d(325);
Console.WriteLine(asd.d.Method.ToString());

// А теперь – деятельность по построению массива описателей типа ссылок.
// Преобразование ОДНОГО объекта, содержащего массив ссылок на функции,
// в массив объектов, содержащих по одной ссылке на функцию.
// Таким образом, для каждой ссылки на функцию делегата в рамках 
// массива делегатов строится свой собственный делегат.
// Естественно что элемент массива уже не является Multicast'овым 
// делегатом. Поэтому с его помощью можно выполнить ОДНУ функцию 
// Multicast'ового делегата.

Console.WriteLine("—In array!––––––––––––––––––––––––– ");
// Вот ссылка на массив делегатов.
// Сюда будем сгружать содержимое Multicast'а.
xDelegate[] dlgArray;
try
{
dlgArray = asd.d.GetInvocationList();
}
catch (System.NullReferenceException e)
{
Console.WriteLine(e.Message + ":Delegate is empty");
return;
}

// Прочитали содержимое массива описателей типа ссылок.
for (i = 0, n = dlgArray.Length; i < n; i++)
{
Console.WriteLine(dlgArray[i].Method.ToString());
// А можно и так, без помощи метода ToString()... С тем же результатом.
//Console.WriteLine(dlgArray[i].Method);
}

// Вот как запускается ОДНА функция из списка ссылок на функции!
// Используются разные варианты запуска.
Console.WriteLine("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
dlgArray[1].DynamicInvoke(new object[]{75}); 
((xDelegate)(dlgArray[2]))(123);
Console.WriteLine("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");


// Используем этот массив для преобразования массива ссылок делегата.
// Выкидываем из массива один элемент.
// Возвращаемое значение (НОВЫЙ делегат с измененным списком)
// может быть "поймано" и другим делегатом! 
asd.d = (xDelegate)xDelegate.Remove(asd.d,dlgArray[1]);
// Таблица ссылок модифицированного делегата сократилась! 
Console.WriteLine("Таблица ссылок сократилась! ");
asd.d(125);

// Через свойство получили ссылку на делегат.
xDelegate dFROMasd = asd.D; 

// Таблица ссылок опустошается!
// Еще бы! Здесь из списка методов одного делегата
// удаляются ВСЕ делегаты, которые встречаются в списке
// методов второго делегата. Буквально: удали из этого списка
// ВСЕ методы, которые встретишь в этом списке!
// Что-то из жизни того парня, который вытягивал сам себя
// за волосы из болота! И если бы НОВЫЙ делегат не был бы
// перехвачен на другую ссылку – весь исходный список
// методов Multicast'ового делегата asd.d был бы потерян. 
dFROMasd = (xDelegate)xDelegate.RemoveAll(asd.d,asd.d);

// В чем и можно убедиться вот таким способом!
try
{
dlgArray = dFROMasd.GetInvocationList();
}
catch (System.NullReferenceException e)
{
Console.WriteLine(e.Message + ":Delegate is empty");
}

// Но только не исходный делегат!
Console.WriteLine("Но только не исходный делегат!");
// В чем и можно убедиться вот таким способом!
try
{
dlgArray = asd.d.GetInvocationList();
}
catch (System.NullReferenceException e)
{
Console.WriteLine(e.Message + ":Delegate is empty");
}

// Вот! Исходный объект класса-делегата – не пустой! 
asd.d(125);
q.QQQ(75,asd.d); 

}//==============================================================
}
}

События

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

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

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

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

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

    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;				
    }
    }
    
    }//______________________________________________________________
    }//==============================================================
    }

    Второй пример:

    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);
    }
    }
    }

    Анонимные методы и делегаты для анонимных методов

    Прежде всего, дадим определение АНОНИМНОГО МЕТОДА. В 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("____________________________________________");
    }
    }
    }

    События и делегаты. Различия

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

    Вернуться к учебному плану