• Microsoft .NET
  • C#.NET
  • Основы, лучшие методы и соглашения реализации событий в C#

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

ОГЛАВЛЕНИЕ

11. Соглашения

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

11.1 Соглашения публикатора события

Имя события

•    Выберите имя, четко передающее изменение состояния, обозначаемое событием.
•    События могут быть разделены на следующие категории: (1) события, возбуждаемые до того, как происходит изменение состояния; и (2) события, возбуждаемые после того, как происходит изменение состояния. Соответственно, нужно выбирать имя события, говорящее, до или после изменения состояния возбуждается событие.

Примеры имен для событий, возбуждаемых до изменения состояния:
•    FileDownloading
•    TemperatureChanging
•    MailArriving

Примеры имен для событий, возбуждаемых после изменения состояния:
•    FileDownloadCompleted
•    TemperatureChanged
•    MailArrived

Производный класс System.EventArgs (где применимо)

•    Для событий, которые должны или могут [когда-нибудь] содержать пользовательские данные о событии, вы должны создать новый класс, который (1) расширяет System.EventArgs, и (2) реализует члены (например, свойства), требуемые для того, чтобы содержать и предоставлять ваши пользовательские данные о событии
•    Вы не должны создавать производный класс EventArgs, только если уверены, что ваше событие никогда не будет содержать данные о событии
•    Именем вашего производного класса EventArgs должно быть имя события с добавленным в конец 'EventArgs'

Примеры имен производных классов от EventArgs:
•    DownloadCompletedEventArgs
•    TemperatureChangedEventArgs
•    MailArrivedEventArgs

Для событий, которые не содержат, и никогда не будут содержать данных, рекомендуется передавать System.EventArgs.Empty. Эта рекомендуемая практика служит для соблюдения соглашений реализации событий даже для событий, не имеющих данных о событии. Если существует вероятность, что ваше событие может когда-нибудь содержать данные о событии, даже если не в первоначальной его реализации, то вы должны создать производный класс от System.EventArgs и использовать его в ваших событиях. Основная польза этой рекомендации в том, что вы однажды сможете добавить данные (свойства) в ваш производный класс, не нарушая совместимость с существующими подписчиками.

Имя обработчика события (делегата)

•    Если вы используете среду разработки .NET 1.x, то вы должны использовать встроенный делегат System.EventHandler
•    Если вы используете среду разработки .NET 2.0 или новее (для обоих публикаторов и подписчиков), то вы можете воспользоваться обобщенным делегатом System.EventHandler<TEventArgs>
•    Если вы создаете свой собственный делегат, то имя делегата должно состоять из имени события с добавленным в конец словом 'Handler(обработчик)'

Примеры имен пользовательских обработчиков событий (делегатов):
•    DownloadCompletedHandler
•    TemperatureChangedHandler
•    MailArrivedHandler

Сигнатура обработчика события (делегата)

Как сказано выше, в "имя делегата", вы должны использовать один из делегатов System.EventHandler, предоставленных средой разработки .NET. В этих случаях сигнатура делегата определяется для вас и автоматически соответствует рекомендуемым соглашениям.

Следующие рекомендации реализованы в делегатах System.EventHandler, предоставленных средой разработки .NET. Если вы создаете свои собственные обработчики событий, то вы должны следовать этим рекомендациям, чтобы сохранять соответствие с реализацией среды разработки .NET.

•    Делегат всегда должен возвращать void.

В случае обработчиков событий нет смысла возвращать значение публикатору события. Помните, что публикаторы событий намеренно ничего не знают о своих подписчиках. Делегат намеренно выполняет функцию посредника между публикатором события и его подписчиками. Следовательно, публикаторы не обязаны ничего знать о своих подписчиках – включая вероятность получения возвращаемых значений. Делегат вызывает каждого подписчика, поэтому любое возвращаемое значение доберется только до делегата и, все равно, никогда не дойдет до публикатора. Этот принцип распространяется на избегание выходных параметров, использующих модификаторы параметра out или ref. Выходные параметры подписчиков никогда не дойдут до публикатора.

•    Первый параметр должен иметь тип object и должен называться sender.

Этот первый параметр используется для хранения ссылки на объект, возбуждающий событие. Передача ссылки на публикатор события позволяет подписчику события различать между несколькими вероятными публикаторами конкретного события. Без ссылки на публикатор метод обработки события никак не сможет распознать или действовать в соответствии с конкретным публикатором, возбудившим конкретное событие.

Тип данных для sender – объект, так как практически любой класс может возбуждать события. Отсутствие строгой типизации параметра sender позволяет применять одну единственную сигнатуру обработчика события для всех событий. При необходимости метод обработки события может привести параметр отправителя к конкретному типу публикатора события.

Статические события должны передавать null в качестве значения отправителя, а не пропускать параметр sender.

•    Второй параметр должен называться 'e' и должен иметь тип System.EventArgs или тип вашего собственного производного класса от System.EventArgs (например,MailArrivedEventArgs).

В случае отменяемых событий второй параметр имеет тип System.ComponentModel.CancelEventArgs или тип вашего собственного производного от него класса. В случае событий, не содержащих данных о событии, вы должны задать System.EventArgs в качестве типа второго параметра. В таких случаях System.EventArgs.Empty указывается в качестве значения этого параметра, когда событие возбуждается. Эта практика рекомендуется для сохранения соответствия соглашению – чтобы все сигнатуры обработчиков  событий включали в себя параметр EventArgs – даже для событий, не имеющих EventArgs. Согласно соглашению, иметь одну единообразную сигнатуру более важно, чем иметь несколько сигнатур обработчиков событий – даже в случаях, когда один из параметров никогда не будет использоваться.

Примеры (без пользовательских данных, отправляемых вместе с событием):

delegate void DownloadCompletedHandler(object sender, EventArgs e);
delegate void TemperatureChangedHandler (object sender, EventArgs e);
delegate void MailArrivedHandler (object sender, EventArgs e);
Примеры (с пользовательскими данными, отправляемыми вместе с событием):
delegate void DownloadCompletedHandler(object sender,
    DownloadCompletedEventArgs e);
delegate void TemperatureChangedHandler (object sender,
    TemperatureChangedEventArgs e);
delegate void MailArrivedHandler (object sender,
    MailArrivedEventArgs e);

Объявление события

•    При условии, что событие подлежит предоставлению в распоряжение кода вне публикующего класса, событие должно объявляться с ключевым словом public (чтобы сделать его доступным для кода вне публикующего класса).
•    Обработчик события, на котором основано событие, указывается в качестве типа события – аналогично тому, как тип данных указывается в типичном объявлении свойства или метода.

Пример (использует встроенный обобщенный делегат System.EventHandler<TEventArgs>):

public event System.EventHandler<mailarrivedeventargs> MailArrived;
Пример (использует пользовательский обработчик события):
public delegate void MailArrivedHandler (object sender,
    MailArrivedEventArgs e);

public event MailArrivedHandler<mailarrivedeventargs> MailArrived;

Метод, возбуждающий событие

•    Вместо возбуждения события, встраиваемого в весь ваш код многократно, рекомендуется создавать отдельный метод, отвечающий за возбуждение события. Затем вы вызываете этот метод во всем вашем коде при необходимости.
•    Именем этого метода должно быть слово On с добавленным в конец именем события.
•    Если ваше событие использует пользовательский подкласс EventArgs, то метод, возбуждающий событие, должен принимать как минимум один параметр, принадлежащий к конкретному подклассу EventArgs, определенному для пользовательских данных события.
•    Для нестатических открытых классов метод должен быть реализован в виде virtual с доступностью, установленной в protected(защищенный), чтобы производные классы легко могли оповещать клиентов, зарегистрированных в базовом классе.
•    Для закрытых классов доступность метода должна быть установлена в private(закрытый), так как возбуждение событий не должно запускаться извне класса.

Примеры (каждый принимает тип пользовательского подкласса EventArgs в качестве аргумента):

OnDownloadCompleted(DownloadCompletedEventArgs) 
{
   // Здесь возбуждается событие
}

private OnTemperatureChanged(TemperatureChangedEventArgs)
{
   // Здесь возбуждается событие
}

virtual OnMailArrived(MailArrivedEventArgs)
{
   // Здесь возбуждается событие
}

11.2 Соглашения подписчика события

Имя метода обработки события

•    Соглашение, реализуемое Visual Studio, когда она автоматически создает заглушку метода обработки события, заключается в том, чтобы называть метод так: (1) имя объекта, возбуждающего событие; за ним следует (2) символ подчёркивания; (3) в конец добавляется имя события.

Примеры:
•    downloader_DownloadCompleted
•    weatherStation_TemperatureChanged
•    mailManager_OnMailArrived
•    Другое соглашение по заданию имени метода обработки события совпадает с описанным выше для задания имени метода, возбуждающего событие в публикаторе. В частности, именем метода должно быть слово On с добавленным в конец именем события.

Примеры:
•    OnDownloadCompleted
•    OnTemperatureChanged
•    OnMailArrived

Сигнатура метода обработки события

•    Сигнатура метода обработки события должна точно совпадать с сигнатурой делегата. Согласно соглашениям обработки событий, а также делегатов EventHandler, предоставленных средой разработки .NET, метод обработки события должен возвращать void, при этом принимая ровно два параметра: переменная типа object, имеющая имя sender, и экземпляр EventArgs (или производного класса), имеющий имя 'e'.

Примеры:

void DownloadManager_DownloadCompleted(object sender, 
    DownloadCompletedEventArgs e)
{
   // здесь идет код обработки события
}

void WeatherStation_TemperatureChanged(object sender,
   TemperatureChangedEventArgs e)
{
   // здесь идет код обработки события
}

void MailMonitor_MailArrived(object sender, MailArrivedEventArgs e)
{
   // здесь идет код обработки события
}

Подписка на событие (код, регистрирующий метод обработки события в событии)

•    Чтобы зарегистрировать метод в событии, используйте синтаксис +=, согласно этому образцу:

EventPublisherObject.EventName += new EventHandlerDelegateName(NameOfMethodToCall);

Пример:

m_MailMonitor.MailArrived += new EventHandler(
    this.MailMonitor_MailArrived);

Предупреждение: Не используйте оператор = при регистрации подписчика события в публикаторе. Такой способ заменит любые/все зарегистрированные в данный момент подписчики события на текущий подписчик. Вместо этого обязательно используйте оператор +=, чтобы добавить текущий подписчик в конец списка вызовов обработчика события.

Отмена подписки на событие (код, отменяющий регистрацию метода обработки события в событии)

•    Чтобы отменить регистрацию метода на событие, используйте синтаксис -=, согласно этому образцу:

EventPublisherObject.EventName -= new EventHandlerDelegateName(NameOfMethodToCall);

Пример:

m_MailMonitor.MailArrived -= new EventHandler(
    this.MailMonitor_MailArrived);

11.3 Соглашения об именовании

Горбатый регистр

Горбатый регистр – соглашение об именовании, при котором первая буква имеет нижний регистр, а каждая последующая "часть слова" начинается с буквы верхнего регистра. По соглашению, имена переменных пишутся в горбатом регистре.
Примеры горбатого регистра: someStringToWrite, ovenTemperature, latitude

Регистр Паскаля

Регистр Паскаля - соглашение об именовании, при котором каждая "часть слова" имени начинается с буквы верхнего регистра, с остальными буквами нижнего регистра, и без символов подчеркивания. По соглашению, имена классов, событий, делегатов, методов, и свойства пишутся в регистре Паскаля.

Примеры регистра Паскаля: MailArrivedEventHandler, AppClosing, MyClassName

12. Шаги к созданию пользовательских событий

Чтобы сделать следующие шаги максимально краткими, дается минимум или вообще не даются пояснения каждого конкретного шага. Объяснения, примеры и соглашения для каждого шага представлены в других местах по всей статье.

12.1 Подготовка публикатора события

Шаг 1: EventArgs – Решите, как ваше событие будет вызывать EventArgs.

•    Включение EventArgs в пользовательские события необходимо для соответствия стандартам публикации события. Однако EventArgs не является техническим требованием – можно создавать, возбуждать и обрабатывать пользовательские события, вообще не использующие EventArgs.
•    Если ваше событие никогда не будет передавать пользовательские данные события, то вы можете воспользоваться встроенным классом System.EventArgs. Позже вам придется задать значение EventArgs.Empty при возбуждении события.
•    Если ваше событие не отменяемое и содержит пользовательские данные события, то вы должны создать класс, расширяющий System.EventArgs. Ваш пользовательский подкласс EventArgs должен содержать любые дополнительные свойства, содержащие данные события.
•    Если ваше событие отменяемое, то вы можете использовать System.ComponentModel.CancelEventArgs – включающий в себя булево свойство Cancel, которое клиенты могут установить в истину, чтобы отменить событие. Вы можете создать производный класс от CancelEventArgs, содержащий свойства для любых дополнительных связанных с событием данных.

Шаг 2: Обработчик события – Решите, какой обработчик события ваше событие будет использовать.

•    У вас есть два основных варианта – создать свой собственный обработчик события (делегат) или использовать один из делегатов EventHandler, предоставляемых средой разработки .NET. Если вы используете один из встроенных обработчиков событий, то вам придется поддерживать меньше кода, и сигнатура вашего обработчика события будет автоматически соответствовать соглашению возвращения void, при этом принимая параметры object sender, и EventArgs e
•    При использовании .NET 1.x обдумайте использование встроенного делегата System.EventHandler.
•    При использовании .NET 2.0 обдумайте использование встроенного обобщенного делегата System.EventHandler<TEventArgs>.

Шаг 3: Объявление события – Решите, какой синтаксис использовать: полеподобный синтаксис или свойствоподобный синтаксис.

•    Полеподобного синтаксиса будет достаточно для многих реализаций пользовательских событий.
•    Обдумайте применение свойствоподобного синтаксиса, если ваш класс предоставляет большое число событий, лишь на несколько из которых, как ожидается, будут подписываться в любой конкретный момент времени.

Шаг 4: Метод, возбуждающий событие – Решите, будете ли вы возбуждать событие из метода, или будете возбуждать его непосредственно в коде.

•    Как правило, рекомендуется возбуждать события из метода, специально предназначенного для этой задачи, а не возбуждать события непосредственно в коде.
Шаг 5: Возбуждение события.
•    Возбуждайте событие непосредственно в коде или вызывайте метод, возбуждающий событие.
•    Перед возбуждением события вы должны создать экземпляр вашего подкласса EventArgs, заполненный связанными с событием данными. Если вы не используете никакой подкласс EventArgs, то вы должны добавить System.EventArgs.Empty вместо пользовательского класса EventArgs, когда вы возбуждаете метод.

12.2 Подготовка подписчика события

Так как эта статья дает образец события (object sender, EventArgs e), реализованного во всех классах среды разработки .NET, следующие шаги помогут вам подключить методы обработки события, работающие почти со всеми событиями среды разработки .NET, в дополнение к пользовательским событиям, которые вы создаете по тому же самому образцу.

Шаг 1: Написание метода обработки события.

•    Определите метод обработки события с сигнатурой, точно совпадающей с сигнатурой делегата, на основе которого определяется метод.
•    При использовании встроенного необобщенного делегата System.EventArgs или обобщенного делегата System.EventHandler<TEventArgs> в объявлении события, результирующая сигнатура автоматически соответствует соглашению возвращения void и принятия параметров (object sender, EventArgs e).

Шаг 2: Создание экземпляра публикатора события.

•    Объявите переменную-член уровня класса, ссылающуюся на класс или объект, публикующий нужное событие.

Шаг 3: Создание экземпляра обработчика события (при необходимости).

•    Если нужное событие основано на пользовательском обработчике события, создайте экземпляр этого обработчика события, передав имя метода обработки события.
•    Этот шаг можно объединить с шагом 4 (следующим) путем использования ключевого слова new(новый) для создания экземпляра делегата в той же строке, в которой делегат регистрируется в событии.

Шаг 4: Регистрация подписчика (метода обработки события) в событии.

•    Любая версия .NET: используйте синтаксис +=, чтобы зарегистрировать обработчик события в событии.
•    .NET 2.0+: Как вариант, с помощью вывода делегата, вы можете непосредственно назначить событию имя метода.
•    .NET 2.0+: Как вариант, если метод обработки события очень короткий (примерно 3 строки кода), вы можете сделать вашу реализацию более читабельной, зарегистрировав код обработки события с помощью "безымянного метода".

Шаг 5: Отмена регистрации подписчика (метода обработки события) в событии.

•    Если подписчик больше не должен получать уведомления о событии от публикатора, вы можете отменить регистрацию подписчика в событии.
•    Этот шаг можно считать необязательным, при условии, что подписчики автоматически лишаются регистрации в публикаторах, когда подписчик уничтожается.