Speak.Me Учить иностранные слова

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-имя-события. Назначение метода в том, чтобы производные классы могли более гибко управлять вызовом члена-события, добавляя необходимый код до и после его вызова.

Метод принимает один параметр — объект с информацией о событии. Как минимум этот метод должен проверять, зарегистрированы ли в делегате члене-событии методы-обработчики, и если да, вызывать член-событие.

Метод, генерирующий событие

У класса должен быть метод, принимающий некоторую входную информацию и в ответ генерирующий событие. Вместо метода это может быть свойство.

Тип, отслеживающий событие

Теперь в любом типе мы можем добавлять методы обработчики события PriceChanged нашего класса Stock:

Здесь, во-первых, создается метод обработчик. Он должен соответствовать делегату EventHandler — возвращать void и принимать два параметра: sender и e.

Sender чаще всего просто игнорируется, но его можно использовать для взаимодействия  с вызывающим объектом (т.е. тем объектом, в котором произошло событие, в нашем примере — объектом класса Stock) и возвращать ему какую либо информацию, использовать его поля, свойства, вызывать его методы.

Второй параметр e — объект с информацией о событии, которую предоставит вызывающий объект (объект, в котором произошло событие, в примере это Stock).

Далее метод регистрируется в качестве обработчика путем добавления его член-событие экземпляра класса Stock:

Отсутствие дополнительной информации о событии

Чаще всего нет необходимости передавать никакую дополнительную информацию о событии. В этом случае стандартный шаблон несколько изменяется:

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

Во-вторых можно использовать не обобщенный делегат EventArgs.

И в-третьих, чтобы не создавать пустой объект типа EventArgs, мы можем воспользоваться его статическим свойством Empty, которое делает это за нас.

Средства доступа событий (Event Accessors)

Средства доступа событий являются всего лишь реализацией функций, выполняемых операторами делегатов += и -=. По умолчанию средства доступа генерируются компилятором автоматически. Т.е. такое, например, объявления события:

компилятор преобразует в частное поле-делегат и пару публичных функций средств доступа, которые будут реализовывать операторы += и -= применительно к частному полю-делегату.

Однако эти действия компилятора можно переопределить, явно задав поле-делегат и средства доступа к нему из члена-события. В этом случае компилятор автоматически ничего проделывать не будет. Возможность явно задать средства доступа дает возможность более гибко управлять процессом добавления обработчиков в делегат.