Возбуждение событий, обработчики событий и использование делегатов
ОГЛАВЛЕНИЕ
Делегаты и их связь с событиями и обработчиками событий.
События
При работе с IDE (интегрированная среда разработки), такой как Visual Studio, большинство событий, особенно касающихся приложений Windows Forms, являются нелинейными. То есть приходится ждать, когда пользователь кликнет по кнопке или нажмет кнопку клавиатуры, чтобы затем отреагировать на это событие. В серверных приложениях приходится ждать (и слушать) входящий сетевой запрос. Эти возможности предоставляются событиями в каркасе .NET. Любой разработчик заметит связь между событиями и делегатами в технической документации. Данная статья предполагает знания .NET Framework 2.0 и Visual Studio 2005 (2008), с целью определения событий и делегатов. Хотя эти темы рассматриваются отдельно, они взаимосвязаны и могут быть объединены для достижения лучшего понимания общего принципа вызова метода при событии или ссылке на этот метод.
Первый раздел разбирает события, их устройство и возможности при их определении в классе. Следующий раздел разбирает делегаты, их устройство, как они объявляются, инстанцируются и используются. Читатель должен заметить, что основная идея – вызов метода для выполнения действия в зависимости от события, и метод, вызываемый по ссылке на этот метод, отличной от указателя функции.
Класс может определять такой вид члена, как событие. Тип, определяющий член-событие, позволяет этому типу (или экземплярам этого типа (класса)) извещать другие объекты, что произошло нечто особенное. Например, класс Button предоставляет событие под именем Click. Когда по объекту Button(кнопка) щелкают мышкой, один или более объектов в приложении могут хотеть получить извещение об этом событии, чтобы выполнить некое действие. События являются типами, обеспечивающими данное взаимодействие. Определение члена-события означает, что класс предоставляет следующие возможности:
• Статический метод типа или метод экземпляра объекта могут регистрировать свой интерес к событию типа.
• Статический метод типа или метод экземпляра объекта могут отменять регистрацию своего интереса к событию типа.
• Зарегистрированные методы будут оповещены при наступлении события
Типы могут оказывать данные функции при определении события, потому что они хранят список зарегистрированных методов. Когда событие наступает, тип оповещает все зарегистрированные методы в списке. Модель событий общеязыковой среды исполнения основана на делегатах. Делегат – безопасный по типу способ вызова методов обратного вызова. Методы обратного вызова являются средствами, путем которых объекты получают оповещения, на которые подписаны.
Событие является сообщением, отправляемым объектом для оповещения об осуществлении действия. Действие может вызываться взаимодействием с пользователем, таким как нажатие кнопки мыши, или же событие может запускаться иной программной логикой. Объект, возбуждающий событие, называется источником события. Объект, перехватывающий и реагирующий на событие, называется получателем события. При передаче события класс источника события не знает, какой объект или метод получит (или обработает) возбужденное им событие. Необходим посредник (или механизм наподобие указателя) между источником и получателем. То есть объект, получающий событие, не может проверить источник события. Каркас .NET определяет специальный тип (делегат), служащий указателем функции. Код ниже показывает пример события и вводит делегаты. Читатель не обязан сразу понять принцип работы кода, так как он объясняется в следующих разделах статьи.
using System;
using System.ComponentModel;
namespace EventSample
{
using System;
using System.ComponentModel;
// Класс, содержащий данные для
// события сигнала. Унаследован от System.EventArgs.
//
public class AlarmEventArgs : EventArgs
{
private readonly bool snoozePressed ;
private readonly int nrings;
//Конструктор.
//
public AlarmEventArgs(bool snoozePressed, int nrings)
{
this.snoozePressed = snoozePressed;
this.nrings = nrings;
}
// Свойство NumRings возвращает количество звонков,
// которые будильник издает при генерации
// события сигнала.
//
public int NumRings
{
get { return nrings;}
}
// Свойство SnoozePressed показывает, нажата ли кнопка повторения сигнала
// при генерации события сигнала.
//
public bool SnoozePressed
{
get {return snoozePressed;}
}
// Свойство AlarmText, содержащее пробуждающее сообщение.
//
public string AlarmText
{
get
{
if (snoozePressed)
{
return ("Проснитесь!!! Время сна закончилось.");
}
else
{
return ("Wake Up!");
}
}
}
}
// Объявление делегата.
//
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
// Класс будильника, возбуждающий событие сигнала.
//
public class AlarmClock
{
private bool snoozePressed = false;
private int nrings = 0;
private bool stop = false;
// Свойство Stop(остановить) показывает, должен ли
// сигнал отключаться.
//
public bool Stop
{
get {return stop;}
set {stop = value;}
}
// Свойство SnoozePressed показывает, нажата ли кнопка повторения сигнала
// при генерации события сигнала.
//
public bool SnoozePressed
{
get {return snoozePressed;}
set {snoozePressed = value;}
}
// Член события имеет тип AlarmEventHandler.
//
public event AlarmEventHandler Alarm;
// Защищенный метод OnAlarm возбуждает событие путем вызова
// делегатов. Источником всегда является this, текущий экземпляр класса.
//
protected virtual void OnAlarm(AlarmEventArgs e)
{
if (Alarm != null)
{
// Вызывает делегаты.
Alarm(this, e);
}
}
// Данный будильник не имеет
// пользовательского интерфейса.
// Механизм включения сигнала моделируется с помощью цикла,
// возбуждающего событие сигнала на каждой итерации
// с задержкой 300 миллисекунд,
// если кнопка повторения сигнала не нажата. Если кнопка повторения сигнала нажата,
// задержка равняется 1000 миллисекундам.
//
public void Start()
{
for (;;)
{
nrings++;
if (stop)
{
break;
}
else if (snoozePressed)
{
System.Threading.Thread.Sleep(1000);
{
AlarmEventArgs e = new AlarmEventArgs(snoozePressed,
nrings);
OnAlarm(e);
}
}
else
{
System.Threading.Thread.Sleep(300);
AlarmEventArgs e = new AlarmEventArgs(snoozePressed,
nrings);
OnAlarm(e);
}
}
}
}
// Класс WakeMeUp(разбуди меня) имеет метод AlarmRang(будильник прозвенел),
// обрабатывающий событие сигнала.
//
public class WakeMeUp
{
public void AlarmRang(object sender, AlarmEventArgs e)
{
Console.WriteLine(e.AlarmText +"\n");
if (!(e.SnoozePressed))
{
if (e.NumRings % 10 == 0)
{
Console.WriteLine(" Позволить будильнику звенеть? Введите Y");
Console.WriteLine(" Нажать кнопку повторения сигнала? Введите N");
Console.WriteLine(" Остановить сигнал? Введите Q");
String input = Console.ReadLine();
if (input.Equals("Y") ||input.Equals("y")) return;
else if (input.Equals("N") || input.Equals("n"))
{
((AlarmClock)sender).SnoozePressed = true;
return;
}
else
{
((AlarmClock)sender).Stop = true;
return;
}
}
}
else
{
Console.WriteLine("Позволить будильнику звенеть? Введите Y");
Console.WriteLine("Остановить сигнал? Введите Q");
String input = Console.ReadLine();
if (input.Equals("Y") || input.Equals("y")) return;
else
{
((AlarmClock)sender).Stop = true;
return;
}
}
}
}
// Управляющий класс, подключающий метод обработки события
// WakeMeUp к событию сигнала объекта Alarm(будильник) с помощью делегата.
// В приложении на базе форм управляющим классом является форма
//
//
public class AlarmDriver
{
public static void Main (string[] args)
{
// Создает экземпляр получателя события.
WakeMeUp w= new WakeMeUp();
// Создает экземпляр источника события.
AlarmClock clock = new AlarmClock();
// Соединяет метод AlarmRang с событием Alarm.
clock.Alarm += new AlarmEventHandler(w.AlarmRang);
clock.Start();
}
}
}
Данный код eventsample.cs компилируется со ссылкой на System.dll:
C:\..\v2.0.50727> csc.exe /reference:System.dll eventsample.cs
Код выполняется: