Основы, лучшие методы и соглашения реализации событий в C# - Регистрация подписчика события, метод обработки события и рекомендации
ОГЛАВЛЕНИЕ
8. Регистрация и отмена регистрации подписчика события
Публикатор события заведомо абсолютно ничего не знает о любом из подписчиков. Следовательно, задача подписчиков – регистрировать или отменять регистрацию самих себя в публикаторе события.
8.1 Регистрация подписчика
Чтобы подписаться на событие, подписчику нужны три вещи:
1. Ссылка на объект, публикующий нужное ему событие
2. Экземпляр делегата, на основе которого определено событие
3. Метод, который будет вызван публикатором, когда он возбудит событие
После этого подписчик регистрирует свой экземпляр обработчика события (делегата) в публикаторе таким образом:
thePublisher.EventName += new
MyEventHandlerDelegate(EventHandlingMethodName);
В вышеприведенной строке:
• thePublisher – ссылка на объект, который будет возбуждать нужное событие. Обратите внимание, как событие EventName вызывается, словно оно является открытым свойством thePublisher.
• Оператор += используется для добавления экземпляра делегата в список вызовов обработчика события в публикаторе. Помните, что несколько подписчиков могут регистрироваться в событии. Используйте оператор += для добавления текущего подписчика в конец списка вызовов нижележащего делегата.
• MyEventHandlerDelegate – ссылка на конкретный делегат обработчика события, который будет использоваться (если это не один из встроенных делегатов EventHandler).
• Наконец, EventHandlingMethodName передает в класс-подписчик имя метода, который будет вызван при возбуждении события.
Предупреждение: Не используйте оператор = при регистрации подписчика события в публикаторе. Такой подход заменит любой/все зарегистрированные в настоящий момент подписчики события на текущий подписчик. Вместо этого обязательно используйте оператор +=, чтобы вызвать добавление текущего подписчика в конец списка вызовов обработчика события.
8.2 Отмена регистрации подписчика
Подписчика можно лишить регистрации в публикаторе таким образом:
thePublisher.EventName -=
EventHandlerDelegate(EventHandlingMethodName);
Оператор -= применяется для удаления экземпляра делегата из списка вызовов в публикаторе.
Регистрация подписчиков автоматически отменяется, когда объект уничтожается – если регистрация подписчика в событии еще не была отменена явно.
9. Метод обработки события
Метод обработки события - метод в подписчике события, выполняемый публикатором события при возбуждении события. Знайте, что некоторая литература, описывающая события в .NET, называет эти методы "обработчиками события", хотя, если быть технически точным, "обработчик события" - это делегат, на котором основано событие – а не любой метод, на который ссылается такой делегат.
Важное требование метода обработки события заключается в том, что его сигнатура должна совпадать с сигнатурой обработчика события (делегата), на основе которого определено событие.
Также вы должны тщательно рассмотреть последствия любых исключений, которые могут быть выброшены или захвачены в методе обработки события. Исключения, не захваченные в методе обработки события, будут переданы публикатору события.
10. Рекомендации по .NET 1.x в сравнении с 2.0+
Концепции и возможности, представленные в данном разделе, были внедрены в версию 2.0 среды разработки .NET. Эти новые возможности означают сокращение и, возможно, упрощение вашего кода при умелом использовании.
Но есть риск, что неправильное использование некоторых из этих возможностей может сделать малопонятным ваш код реализации события. Например, если вы воспользуетесь "безымянным методом" (изложен ниже), состоящим из 30+ строк кода, ваша реализация события, скорее всего, будет намного труднее для понимания, чем эквивалентная реализация, помещающая эти 30+ строк в именованный метод.
Важно понимать, что эти концепции и возможности 2.0+ не вносят никаких кардинальных изменений в способ реализации событий в приложениях среды разработки .NET. Наоборот, они предназначены для упрощения способа реализации событий.
10.1 Обобщенные
Наряду со специфичными для обобщенных возможностями, представленными в других местах данной статьи (например, System.EventHandler<T>), нужно заметить, что любые способы реализации событий, каким-либо образом основанные на обобщенных, не будут доступны в приложениях .NET 1.x, так как обобщенные были впервые введены в версии 2.0 среды разработки .NET.
10.2 Вывод делегатов
Компилятор C# 2.0 (и новее) достаточно разумен, чтобы определить тип делегата, на основе которого реализовано конкретное событие. Эта возможность "вывода делегатов" позволяет вам пропускать объявление требуемого делегата в коде, регистрирующем метод обработки события в событии.
Рассмотрим следующий код 1.x, регистрирующий метод обработки события в событии. Этот код явно создает экземпляр обработчика события (делегата), чтобы зарегистрировать связанный с ним метод в событии.
thePublisher.EventName += new MyEventHandlerDelegate(EventHandlingMethodName);
Следующий код 2.0+ использует вывод делегата для регистрации того же самого метода в событии. Заметьте, что следующий код выглядит регистрирующим метод обработки события непосредственно в событии.
thePublisher.EventName += EventHandlingMethodName;
При назначении имени метода непосредственно событию таким образом компилятор C# следит за тем, чтобы сигнатура метода совпадала с сигнатурой обработчика события, на котором основано событие. Затем компилятор C# вставляет нужный код регистрации делегата (т.е., ... += newMyEventHandlerDelegate(EventHandlingMethodName);) в выходную сборку.
Компилятор C# позволяет использовать этот упрощенный синтаксис, а не какие-либо изменения основных способов реализации событий в среде разработки .NET. События в C# 2.0 (и новее) не могут напрямую ссылаться на методы. Компилятор добавляет [все еще] требуемый синтаксис делегата в выходную сборку - словно мы явно создали экземпляр делегата.
10.3 Безымянные методы
Безымянный метод – блок кода, который вы передаете делегату (вместо передачи имени метода, на который будет ссылаться делегат). Когда компилятор C# встречает безымянный метод, он создает в выходной сборке полноценный метод, содержащий переданный вами блок кода. Компилятор задает имя для метода, затем создает ссылку на этот [новый] метод из связанного с ним экземпляра делегата (все это происходит в выходной сборке). Метод называется безымянным, потому что вы используете метод, не зная его имени (метод не имеет имени в вашем исходном коде).
Безымянные методы дают возможность писать более простой код. Рассмотрим следующий код, регистрирующий короткий метод обработки события в событии:
static void EventHandlingMethod(object sender, EventArgs e)
{
Console.WriteLine("Handled by a named method");
}
thePublisher.EventName += new MyEventHandlerDelegate(EventHandlingMethod);
Вышеуказанный код можно переписать с использованием безымянного метода так:
thePublisher.EventName += delegate {
Console.WriteLine("Handled by anonymous method");
};
Безымянные методы предназначены для упрощения кода. Упрощение может произойти, когда блок кода относительно короткий. В примере выше вариант кода, использующий синтаксис безымянного метода, легко читается, так как не нужно находить какой-то отдельный метод обработки события, чтобы понять, как подписчик будет реагировать при возбуждении события. Однако синтаксис безымянного метода может быть более тяжелым для понимания (более, чем код, ссылающийся на именованный метод) в случаях, когда блок кода состоит из множества строк кода. Некоторые авторы считают, что блоки кода, содержащие более трех или четырех строк кода, не должны реализовываться в виде безымянных методов. Эти более длинные блоки кода должны входить в именованные методы для повышения читабельности.
Чтобы обобщить представленные выше варианты, следующий код демонстрирует три варианта регистрации метода обработки события в событии. Первый демонстрирует явный подход, работающий во всех версиях среды разработки .NET. Второй демонстрирует вывод делегата. Третий демонстрирует применение безымянного метода:
// Вариант 1 – явное создание делегата с использованием именованного метода
thePublisher.EventName += new MyEventHandlerDelegate(EventHandlingMethod);
// Вариант 2 – вывод делегата
thePublisher.EventName += EventHandlingMethod;
// Вариант 3 – безымянный метод
thePublisher.EventName += delegate(object sender, EventArgs e) {
Console.WriteLine("handled by anonymous method");
// При необходимости вы можете здесь обращаться к отправителю и параметрам e
};
// Метод обработки события, используемый в вариантах 1 и 2
static void EventHandlingMethod(object sender, EventArgs e)
{
Console.WriteLine("Handled by a named method");
}
10.4 Частичные классы
Частичные классы имеют отношение к реализации событий в том, что Visual Studio поместит заглушки кода регистрации события и метода обработки события в файлы частичного класса, связанного с конкретным классом Windows Form. Нажмите здесь, чтобы перейти в раздел 15.1, подробнее описывающий реализацию событий в частичных классах.