События - соглашение об именах и стиль
Я узнаю о событиях / делегатов в C#. Могу я спросить ваше мнение о стиле именования / кодирования, который я выбрал (взято из книги Head First C#)?
Завтра учу друга об этом и пытаюсь придумать самый элегантный способ объяснения понятий. (думал, что лучший способ понять предмет - попытаться научить его!)
class Program
{
static void Main()
{
// setup the metronome and make sure the EventHandler delegate is ready
Metronome metronome = new Metronome();
// wires up the metronome_Tick method to the EventHandler delegate
Listener listener = new Listener(metronome);
metronome.OnTick();
}
}
public class Metronome
{
// a delegate
// so every time Tick is called, the runtime calls another method
// in this case Listener.metronome_Tick
public event EventHandler Tick;
public void OnTick()
{
while (true)
{
Thread.Sleep(2000);
// because using EventHandler delegate, need to include the sending object and eventargs
// although we are not using them
Tick(this, EventArgs.Empty);
}
}
}
public class Listener
{
public Listener(Metronome metronome)
{
metronome.Tick += new EventHandler(metronome_Tick);
}
private void metronome_Tick(object sender, EventArgs e)
{
Console.WriteLine("Heard it");
}
}
Примечание: код изменен с http://www.codeproject.com/KB/cs/simplesteventexample.aspx
7 ответов
Есть несколько моментов, которые я бы упомянул:
Metronome.OnTick, кажется, назван неправильно. Семантически, "OnTick" говорит мне, что он будет вызываться, когда он "Tick", но это не совсем то, что происходит. Я бы назвал это "Иди" вместо этого.
Обычно принятая модель будет заключаться в следующем. OnTick
это виртуальный метод, который вызывает событие. Таким образом, вы можете легко переопределить поведение по умолчанию в унаследованных классах и вызвать базу, чтобы вызвать событие.
class Metronome
{
public event EventHandler Tick;
protected virtual void OnTick(EventArgs e)
{
//Raise the Tick event (see below for an explanation of this)
var tickEvent = Tick;
if(tickEvent != null)
tickEvent(this, e);
}
public void Go()
{
while(true)
{
Thread.Sleep(2000);
OnTick(EventArgs.Empty); //Raises the Tick event
}
}
}
Кроме того, я знаю, что это простой пример, но если к нему не подключены слушатели, ваш код будет Tick(this, EventArgs.Empty)
, Вы должны по крайней мере включить нулевую охрану для проверки на слушателей:
if(Tick != null)
Tick(this, EventArgs.Empty);
Однако это все еще уязвимо в многопоточной среде, если слушатель незарегистрирован между защитником и вызовом. Лучше всего сначала захватить текущих слушателей и вызвать их:
var tickEvent = Tick;
if(tickEvent != null)
tickEvent(this, EventArgs.Empty);
Я знаю, что это старый ответ, но так как он все еще собирает голоса, вот способ C# 6 делать вещи. Вся концепция "сторожа" может быть заменена условным вызовом метода, и компилятор действительно делает правильную вещь в отношении захвата слушателей:
Tick?.Invoke(this, EventArgs.Empty);
Microsoft фактически написала обширный набор правил именования и поместила его в библиотеку MSDN. Вы можете найти статьи здесь: Руководство по именам
Помимо общих правил использования заглавных букв, здесь есть то, что имеет место для "Событий" на странице " Имена членов типа":
Назовите события с глаголом или глагольной фразой.
Дайте названиям событий понятие до и после, используя настоящее и прошедшее время. Например, событие закрытия, которое вызывается до закрытия окна, будет называться Закрытием, а событие, которое возникает после закрытия окна, будет называться Закрытым.
Не используйте префиксы или суффиксы "До" или "После" для обозначения событий до и после события.
Называйте обработчики событий (делегаты, используемые в качестве типов событий) с суффиксом EventHandler.
Используйте два параметра с именем sender и e в сигнатурах обработчиков событий.
Параметр отправителя должен иметь тип Object, а параметр e должен быть экземпляром или наследоваться от EventArgs.
Назовите классы аргумента события с суффиксом EventArgs.
Я бы сказал, что лучшее руководство по событиям в целом, включая соглашения об именах, находится здесь.
Это соглашение, которое я принял, вкратце:
- Имена событий обычно заканчиваются глаголом, оканчивающимся на -ing или -ed (закрытие / закрытие, загрузка / загрузка)
- Класс, который объявляет событие, должен иметь защищенное виртуальное On[EventName], которое должно использоваться остальной частью класса для вызова события. Этот метод может также использоваться подклассами, чтобы вызвать событие, и также перегружен, чтобы изменить логику создания события.
- Часто возникает путаница относительно использования "обработчика" - для согласованности все делегаты должны иметь постфикс с обработчиком, постарайтесь избегать вызова методов, которые реализуют обработчик как "обработчики"
- Соглашение по именованию VS по умолчанию для метода, который реализует обработчик, - EventPublisherName_EventName.
Интересно, что Microsoft, кажется, нарушает свои собственные соглашения об именах с помощью имен обработчиков событий, сгенерированных Visual Studio.
После многих лет использования событий в.Net я обнаружил, что при каждом вызове необходимо периодически проверять наличие нулевого обработчика события. Мне еще предстоит увидеть кусок живого кода, который делает что угодно, но не вызывает событие, если оно пустое.
Я начал делать фиктивный обработчик для каждого события, которое я создаю, чтобы избавить от необходимости делать нулевую проверку.
public class Metronome
{
public event EventHandler Tick =+ (s,e) => {};
protected virtual void OnTick(EventArgs e)
{
Tick(this, e); // now it's safe to call without the null check.
}
}
Выглядит хорошо, кроме того факта, что OnTick
не следует типичной модели вызова событий. Как правило, On[EventName]
поднимает событие один раз, как
protected virtual void OnTick(EventArgs e)
{
if(Tick != null) Tick(this, e);
}
Подумайте о создании этого метода и переименовании существующегоOnTick
"метод"StartTick
", а не вызывать Tick
прямо из StartTick
, вызов OnTick(EventArgs.Empty)
от StartTick
метод.
В вашем случае это может быть:
class Metronome {
event Action Ticked;
internalMethod() {
// bla bla
Ticked();
}
}
Выше примера использования ниже соглашения, самоописание;]
Источник событий:
class Door {
// case1: property change, pattern: xxxChanged
public event Action<bool> LockStateChanged;
// case2: pure action, pattern: "past verb"
public event Action<bool> Opened;
internalMethodGeneratingEvents() {
// bla bla ...
Opened(true);
LockStateChanged(false);
}
}
КСТАТИ. ключевое слово event
не является обязательным, но позволяет отличать "события" от "обратных вызовов"
Слушатель событий:
class AlarmManager {
// pattern: NotifyXxx
public NotifyLockStateChanged(bool state) {
// ...
}
// pattern: [as above]
public NotifyOpened(bool opened) {
// OR
public NotifyDoorOpened(bool opened) {
// ...
}
}
И связывание [код выглядит дружественным к человеку]
door.LockStateChanged += alarmManager.NotifyLockStateChanged;
door.Moved += alarmManager.NotifyDoorOpened;
Даже ручная отправка событий "читабельна".
alarmManager.NotifyDoorOpened(true);
Иногда более выразительным может быть "глагол + инг"
dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting;
Какую бы конвенцию вы ни выбрали, будьте последовательны с ней.