C#: события (Events)
Событие — это член типа, с помощью которого этот тип (или его экземпляр) может уведомлять другие объекты о наступлении особых ситуациях (т.е. уведомлять о наступлении определенных «событий»), например, клик по кнопке, получение письма и т.д. Другие объекты, получив такое уведомление, смогут на него отреагировать, выполнив определенные действия. События — член типа, обеспечивающий такого рода взаимодействие объектов.
Таким образом, тип, в котором определено событие, должен позволять:
- регистрировать обработчики событий — статические или экземплярные методы, заинтересованные в получении уведомления о событии и выполняющие в ответ на это событие определенные действия;
- отменять регистрацию обработчиков событий;
- уведомлять зарегистрированные обработчики о том, что событие произошло.
Модель событий в C# основана на делегатах. По сути, событие — член типа, который ссылается на экземпляр любого объявленного ранее типа делегата. Регистрация обработчиков события осуществляется путем добавления методов-обработчиков в этот делегат. Отмена регистрации — путем удаления методов-обработчиков из делегата. А уведомление зарегистрированных обработчиков о наступлении события происходит путем вызова этого делегата.
Событие, как член типа, объявляется с помощью ключевого слова event . Событию также назначается область видимости с помощью одного из модификаторов доступа, тип — любой ранее объявленный тип делегата, и имя — любой допустимый идентификатор. При возникновении события нужно просто вызвать член-событие объекта, ссылающийся на делегат с обработчиками:
Особенность члена-события состоит в том, что даже если он объявлен с модификатором public вызван как делегат он может быть только внутри родительского типа, т.к. только родительский тип может генерировать событие. Внешние объекты могут только добавлять и удалять из члена-события свои обработчики с помощью операторов += и -= , как в обычный делегат.
Члены события могут быть виртуальными (virtual), переопределенными (overridden), абстрактными (abstract), запечатанными (sealed), а также статическими (static).
Стандартный шаблон события
.NET Framework предусматривает стандартный шаблон написания событий. Он предусматривает несколько моментов. Рассмотрим их по порядку на следующем примере:
Тип для хранения информации о событии
При возникновении события объект, в котором оно возникло, должен передать дополнительную информацию о событии объектам-получателям уведомления. Эта информация инкапсулируется в отдельный класс. По соглашению этот класс наследуется от типа System.EventArgs , а имя его должно заканчиваться суффиксом EventArgs .
Класс System.EventArgs содержит всего один член (статическое поле Empty ) и служит базовым классом для новых типов, посредством которых передается информация о событии. Выглядит он примерно так:
В большинстве случаев передавать дополнительную информацию о событии не требуется, т.к. ее просто нет. В таких случаях нет необходимости создавать класс для хранения информации о событии. Нет необходимости даже создавать экземпляр базового класса EventArgs , можно просто воспользоваться его статическим полем Empty , которое содержит ссылку на экземпляр класса.
Определение члена-события
Далее в соответствии с шаблоном необходимо определить класс объектов, которые будут генерировать события. Для этого, как отмечалось, нужно классу добавить член-событие.
Типом для члена-события выступает предопределенный в .NET Framework обобщенный делегат System.EventHandler<TEventArgs> :
Делегат принимает два параметра. В качестве первого параметра source в делегат передается сам объект, в котором произошло событие, т.е. при вызове делегата в него передается this . В целях поддержки наследования передается он с базовым типом object , а не с конкретным типом, как это сделано для второго параметра.
Второй параметр — это объект с информацией о событии (класс которого создавался на первом этапе). Ему уже указывается конкретный тип, который передается в качестве параметра типа в делегат ( TEventArgs ). По соглашению в делегате и в методе-обработчике этот параметр называется e .
Делегат EventHandler возвращает void , соответственно методы-обработчики тоже должны возвращать void .
Метод, вызывающий член-событие
В соответствии с шаблоном в классе, генерирующем событие, должен быть виртуальный защищенный метод, вызываемый из кода класса и его потомков при возникновении события. По соглашению этот метод получает название, соответствующее маске On—имя-события . Назначение метода в том, чтобы производные классы могли более гибко управлять вызовом члена-события, добавляя необходимый код до и после его вызова.
Метод принимает один параметр — объект с информацией о событии. Как минимум этот метод должен проверять, зарегистрированы ли в делегате члене-событии методы-обработчики, и если да, вызывать член-событие.