События - соглашение об именах и стиль

Я узнаю о событиях / делегатов в 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 Framework 1.1)

После многих лет использования событий в.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;

Какую бы конвенцию вы ни выбрали, будьте последовательны с ней.

Другие вопросы по тегам