В одной из прошлых статей мы поговорили о делегатах. Мы выяснили, что сами по себе делегаты используются довольно редко. Чаще в исполнении с событиями. В виду этого сейчас мы и займемся изучением довольно простого механизма обратных вызовов с помощью событийного аппарата c#.
Для чего же существуют события. Все мы, еще до того, как начать программировать, работали с компьютером в качестве пользователя. Многие из нас уже и не застали те времена, когда еще не было операционной системы Windows. Поэтому с событиями мы сталкиваемся на каждом шагу. К примеру щелкая на иконке некоторого приложения, мы запускаем это приложение. Именно щелчок мыши и есть событие. ОС зафиксировала данное событие и в ответ выполнило определенные действия, в данном случае запуск требуемого приложения. Также, примером события может служить зажим некоторого файла на рабочем столе и последующим перетаскиванием его в другую область. Здесь вообще налицо целый набор событий. Что касается программирования, то чаще пользовательские события нацелены на «вылов» момента изменения некоторых данных, перемещение контролов, заполнение данными некоторых контролов и т.д. Одним словом, событие – все что угодно, что может произойти с чем-либо.
Теперь рассмотрим реальный пример.
В нашей программе мы можем вносить изменения в единственное поле некоторого пользовательского класса. Причем, мы хотим быть оповещенными о каждом изменении значения этого поля в тот самый момент внесения изменений.
Первое, с чего следует начать, это подумать над методом-обработчиком. А именно, какую функциональность мы ожидаем от него получить. Предположим, что нас интересует лишь сам факт изменения данных. Для консольного приложения достаточно будет вывести сообщение на консоль:
//Обработчик события MyEvent
static void Handler()
{
Console.WriteLine("Имя изменилось!");
Console.ReadKey();
}
Следующим шагом мы анализируем сигнатуру данного обработчика – без возвращаемых значений и без принимаемых параметров.
Забегая наперд скажу, что вызовом данного обработчика будет заниматься делегат. Почему делегат, а не прямой вызов. Дело в том, что вся соль событий заключается в том, что само событие не знает ничего об обработчиках и наперед не известно какие имеено методы будут обрабатывать событие. Поэтому и используется делегат. Мы подошли к созданию делегата. Сигнатура делегата уже известна (взгляните на сигнатуру обработчика):
//пользовательский делегат
delegate void MyDel();
Теперь займемся созданием пользовательского класса
class MyClass
{
//поле
string name;
//событие
public event MyDel MyEvent;
//конструктор
public MyClass(string name)
{
this.name = name;
}
}
Как видим, он имеет поле name и событие MyEvent. Как вы уже увидели, событие объявляется с помощью ключевого слова event и указанием типа делегата.
Дальше в данном классе определим некоторый метод, который будет позволять вносить изменения в поле name:
class MyClass
{
//поле
string name;
//событие
public event MyDel MyEvent;
//конструктор
public MyClass(string name)
{
this.name = name;
}
//метод для редактирования поля name
public void ChangeName(string name)
{
this.name = name;
//генерирование события
if (MyEvent != null)
MyEvent();
}
}
Здесь обратите внимание на вызов события. В условии мы проверяем на факт подписавшихся обработчиков на данное событие. Если ни один метод не подписан в качестве обработчика на данное событие, то мы получим исключение при попытке вызова события. Поэтому, вызывая пользовательские события всегда выполняйте проверку на null.
Но как подписаться на событие. Очень просто. Для этого используется оператор "+=", соответсвенно снятие метода с регистрации – "-=".
using System;
//пользовательский делегат
delegate void MyDel();
class Program
{
//Точка входа в программу
static void Main(string[] args)
{
//Создаем объект класса MyClass
MyClass myClass = new MyClass("Первое имя");
//Подписываемся на событие MyEvent
myClass.MyEvent += new MyDel(Handler);
//Изменяем поле name
myClass.ChangeName("Новое имя");
}
//Обработчик события MyEvent
static void Handler()
{
Console.WriteLine("Имя изменилось!");
Console.ReadKey();
}
}
class MyClass
{
//поле
string name;
//событие
public event MyDel MyEvent;
//конструктор
public MyClass(string name)
{
this.name = name;
}
//метод для редактирования поля name
public void ChangeName(string name)
{
this.name = name;
//генерирование события
if (MyEvent != null)
MyEvent();
}
}

Рис 1. Результаты работы программы
Все, что на данный момент могло остаться не понятным, так это
//Создаем объект класса MyClass
MyClass myClass = new MyClass("Первое имя");
//Подписываемся на событие MyEvent
myClass.MyEvent += new MyDel(Handler);
//Изменяем поле name
myClass.ChangeName("Новое имя");
Все просто. Создали объект класса MyClass, подписались на его событие и просто, как ни в чем не бывало, вызвали его метод, никто ж не запрещает вызывать открытые методы. И вот в этот момент и наступит событие, которое мы удачно обработаем в методе Handler().
Конечно, в таком исполнении мы не можем определить информацию о самом событии из-зи того, что наш обработчик не принимает параметры. Я намеренно не хотел усложнять пример, но вы уже знаете в какой способ возможно делегатом передавать в вызывающий метод входные параметры.
В итоге мы получили возможность оповещения нас сразу после изменения значения поля name. Если мы хотим получать уведомление до началом внесения изменений, то данный метод следует переписать так:
public void ChangeName(string name)
{
//генерирование события
if (MyEvent != null)
MyEvent();
this.name = name;
}
События на первых порах даются не очень просто. Причина в том, что очень сложно подобрать простой пример, где кроме событий не будет ничего. Их понимание – это всего лишь вопрос времени. Для более полного понимания механизма их реализации я советую посмотреть проекты windowsForms с их автоматической генерацией обработчиков событий при двойном нажатии кнопки мыши на любом контроле (к примеру Button). Заметьте, что вовсе не обязательно обработчики событий создавать в классе в котором производится подписка на событие. Место размещения обработчика может быть любым классом. Сам обработчик может быть даже статическим. Но в таком случае позаботьтесь об указании правильного квалифицированного имени обработчика.
Об отписке от событий