Public class EntryPoint

Private void PlaySomething(object source, PlayEventArgs args)

Public class CorePlayer

Public class PlayerUI

Public string Filename

{ get { return filename; }

}

}

{ // Определить событие для уведомления о воспроизведении.

public event EventHandler<PlayEventArgs> PlayEvent;

public void UserPressedPlay()

{ OnPlay();

}

protected virtual void OnPlay()

{ // Инициировать событие.

EventHandler<PlayEventArgs> localHandler = PlayEvent;

if (localHandler!= null)

{ localHandler(this, new PlayEventArgs("somefile.wav"));

}

}

}

{ public CorePlayer()

{ ui = new PlayerUI();

// Регистрация обработчика события.

ui.PlayEvent += this.PlaySomething;

}

{ // Воспроизведение файла.

}

private PlayerUI ui;

}

{ static void Main()

{ CorePlayer player = new CorePlayer();

}

}

Несмотря на то, что синтаксис этого простого события может показаться усложненным, общая идея состоит в создании четко определенного контракта, через который все заинтересованные стороны уведомляются о том, что пользователь желает воспроизвести файл. Этот контракт инкапсулирован внутри класса PlayEventArgs, который унаследован от System.EventArgs (как описано ниже). События накладывают определенные правила на использование делегатов. Делегат должен что-нибудь возвращать и должен принимать два аргумента, как показано в методе PlaySomething из предыдущего примера. Первый аргумент – это ссылка на объект, представляющий сторону, которая генерирует сообщение, а второй аргумент – тип, унаследованный от System.EventArgs. В этом производном классе определяются все специфичные для события аргументы.

На заметку! В.NET 1.1 приходилось явно определять тип делегата, стоящего за событием. Начиная с.NET 2.0, можно использовать новый обобщенный делегат EventHandler<T>, защищающий от этой рутинной работы.

Обратите внимание на способ определения события внутри класса PlayerUI с применением ключевого слова event. За этим ключевым словом сначала следует определенный делегат события, а за ним – имя события, в данном случае PlayEvent. Также отметьте, что член-событие объявлено с использованием обобщенного делегата EventHandler<T>.

При регистрации обработчиков с применением операции += в качестве сокращения, можно предоставлять только имя метода для вызова, а компилятор создаст экземпляр EventHandler<T>, используя группу методов для делегирования правил присваивания, которые упоминались в предыдущем разделе. После операции += можно дополнительно указать выражение, создающее новый экземпляр EventHandler<T>, как это делалось при создании экземпляров делегатов, но если компилятор предлагает показанное сокращение, то зачем применять громоздкий синтаксис, затрудняющий чтение кода?

Идентификатор PlayEvent означает две совершенно разные вещи, в зависимости от того, с какой точки зрения его рассматривать. С точки зрения генератора события – в данном случае, PlayerUI – событие PlayEvent используется в точности как делегат. Такое его применение можно видеть внутри метода OnPlay. Обычно метод, названный OnPlay, вызывается в ответ на щелчок на кнопке пользовательского интерфейса. Он уведомляет всех зарегистрированных слушателей о вызове через событие (делегат) PlayEvent.

На заметку! При генерации событий существует популярный подход – инициировать их внутри метода protected virtual по имени On<событие>, где <событие> заменяется именем события, в данном случае – OnPlay. Подобным образом производные классы могут легко модифицировать действия, предпринимаемые, когда должно быть инициировано событие. В С# необходимо проверить событие на равенство null, прежде чем вызывать его, иначе будет сгенерировано исключение NullReferenceException. Перед проверкой на null метод OnPlay создает локальную копию события. Это позволяет избежать условия состязаний, когда событие устанавливается в null из другого потока после выполнения проверки на null и перед генерацией события.

Как видно в конструкторе CorePlayer, со стороны потребителя события идентификатор PlayEvent используется совершенно иначе.

Такова базовая структура событий. Как упоминалось ранее, события.NET – это сокращения для создания делегатов и контрактов, с которыми нужно регистрировать эти делегаты. В доказательство этого можно просмотреть код IL, полученный в результате компиляции предыдущего примера. "За кулисами" компилятор генерирует два метода addOnPlay и removeOnPlay, которые вызываются, когда используются перегруженные операции += и -=. Эти методы управляют добавлением и удалением делегатов в цепочке делегатов событий. В действительности компилятор С# не позволяет вызывать эти методы явно, так что должны использоваться перегруженные операции. Может возникнуть вопрос, а есть ли какой-нибудь способ контролировать тело этих функций-членов, как это делается со свойствами? Ответ – да, и используемый для этого синтаксис подобен синтаксису свойств.

Ниже показан измененный код класса PlayerUI, в котором демонстрируется явный способ обработки добавления и удаления операций событий.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: