Поиск

Определение событий с помощью делегатов

Практически во всех Windows-приложениях требуется асинхронная обработка событий. Некоторые из этих событий связаны с самой ОС, например, когда Windows посылает сообщения в очередь сообщений при- \ ложения при том или ином взаимодействии пользователя с приложени-I ем. Другие больше связаны с предметной областью, например, когда \ нужно распечатать счет при обновлении заказа.

, Работа с событиями в С# соответствует модели "издатель — подписчик", где класс публикует событие, которое он может инициировать, и любые классы могут подписаться на это событие. При инициации события исполняющая среда следит за тем, чтобы уведомить всех подписчиков о возникновении события.

Метод, вызываемый при возникновении события, определяется делегатом. Однако нужно помнить об ограничениях, которые налагаются на делегаты, используемые для этих целей. Во-первых, нужно чтобы такой делегат принимал два аргумента. Во-вторых, эти аргументы всегда должны представлять два объекта: тот, что инициировал событие (издатель), и информационный объект события, который должен быть производным от класса EventArgs .NET Framework.

Скажем, мы хотим отслеживать изменения объемов запасов. Мы создаем класс InventoryManager, который будет всегда использоваться при обновлении запасов. Этот класс должен публиковать события, возника-, ющие при всяком изменении запасов вследствие закупок, продаж и других причин. Тогда любой класс, которому нужно отслеживать изменения, должен подписаться на эти события. Вот как это делается на С# при помощи делегатов и событий:

using System;
class InventoryChangeEventArgs : EventArgs
{
public InventoryChange£ventArgs(string sku, int change)
{
this.sku = sku;
this.change = change; >
string sku; public string Sku {
get
{
return sku;
} }
int change; public int Change {
get {
return change; } } >
class InventoryManager // Издатель. {
public delegate void InventoryChangeEventHandler
(object source, InventoryChangeEventArgs e); public
event InventoryChangeEventHandler OnlnventoryChangeHandler;
public void Updatelnventory(string sku, int change) {
if (0 == change)
return; // Ничего обновлять не нужно.
// Здесь должен быть код для обновления БД.
InventoryChangeEventArgs e = new
InventoryChangeEventArgs(sku, change);
if (OnlnventoryChangeHandler != null) OnInventoryChangeHandler(this, e); } }
class InventoryWatcher // Подписчик.
{
public InventoryWatcher(InventoryManager inventoryManager) {
this.inventoryManager = inventoryManager;
inventoryManager.OnlnventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler
(Onlnvento ryChange); } void OnInventoryChange(object
source, InventoryChangeEventArgs e)
I <
\ int change = e.Change;
\ Console.WriteLineC'Part '{0}' was {1} by {2} units", \ e.Sku,
change > 0 ? "increased" ; "decreased",
Math.Abs(e.Change));
}
InventoryManager inventoryManager;
}
class EventslApp {
public static void MainQ <
InventoryManager inventoryManager = new InventoryManagerO;
InventoryWatcher inventoryWatch =
new InventoryWatcher(inventoryManager);
inventoryManager.Updatelnventory("111 006 116", -2);
inventoryManager.Updatelnventory("111 005 383", 5); > >
Рассмотрим первые два члена класса InventoryManager:
public delegate void InventoryChangeEventHandler
(object source, InventoryChangeEventArgs e);
public event InventoryChangeEventHandler OnlnventoryChangeHandler;

Первая строка кода — это делегат, вы можете узнать его по определению сигнатуры метода. Как я говорил, все делегаты, используемые с событиями, должны быть определены с двумя аргументами: объект-издатель (в данном случае source) и информационный объект события (производный от EventArgs объект). Во второй строке указано ключевое слово event, член, имеющий тип указанного делегата, и метод (или методы), которые будут вызываться при возникновении события.

Update Inventory, — последний метод класса Inventory Manager — вызывается при каждом изменении запасов. Как видите, он создает объект типа InventoryChangeEventArgs, который передается всем подписчикам и описывает возникшее событие.
Теперь рассмотрим еще две строки кода:

if (OnlnventoryChangeHandler != null)
OnInventoryChangeHandler(this, e); I

Условный оператор // проверяет, есть ли подписчики, связанные с / методом OnlnventoryChangeHandler. Если это так, т. e. OnlnventoryChangeHandler не равен null, инициируется событие. Вот и все, что касается издателя. Теперь рассмотрим код подписчика.

Подписчик в нашем случае представлен классом Inventory Watcher. Ему нужно выполнять две простые задачи. Во-первых, он должен указать себя как подписчик, создав новый экземпляр делегата типа Inventory-Manager. InventoryChange Event Handler и добавив этот делегат к событию InventoryManager.OnlnventoryChangeHandler. Обратите особое внимание на синтаксис: чтобы добавить себя к списку подписчиков, он использует составной оператор присваивания +=, чтобы не уничтожить предыдущие подписчики.

inventoryManager.OnlnventoryChangeHandler +=
new InventoryManager.InventoryChangeEventHandler (OnlnventoryChange);

Единственный аргумент, указываемый здесь, — имя метода, вызываемого при возникновении события.

Последняя задача, которую должен выполнить подписчик, — это реализовать свой обработчик событий. В данном случае обработчик ошибок — InventoryWatcher.OnlnventoryChange — выводит сообщение об изменении запасов с указанием номера позиции.

И, наконец, в коде этого приложения при каждом вызове метода InventoryManager. Update Inventory создаются экземпляры классов Inventory-Manager и Inventory Watcher т автоматически инициируется событие, которое приводит к вызову метода InventoryWatcher.OnlnventoryChanged.

Подведем итоги

Делегаты — привязанные к типу управляемые объекты — играют в С# ту же роль, что и указатели функций в C++. Отличие делегатов от классов и интерфейсов в том, что они ссылаются на один метод и определяются в период выполнения. Делегаты широко применяются для асинхронной обработки и добавления нестандартного кода к коду классов. Делегаты могут использоваться для многих целей, включая методы обратного вызова, определение статических методов и обработку событий.