Подтвердить что ты не робот

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

Я узнаю о событиях/делегатах на С#. Могу ли я спросить ваше мнение об именовании/кодировке, выбранном мной (взято из книги Head First С#)?

Я учу друга об этом завтра, и я пытаюсь придумать самый элегантный способ объяснить понятия. (подумал, что лучший способ понять предмет - попытаться научить его!)

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");
        }
    }

n.b. Код реорганизуется из http://www.codeproject.com/KB/cs/simplesteventexample.aspx

4b9b3361

Ответ 1

Есть несколько моментов, которые я бы упомянул:

Metronome.OnTick, похоже, неправильно назван. Семантически, "OnTick" говорит мне, что он будет вызываться, когда он "Tick", но на самом деле это не так. Вместо этого я бы назвал это "Go".

Обычно принятая модель, однако, будет заключаться в следующем. 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);

Я знаю, что это старый ответ, но поскольку он все еще собирает upvotes, здесь С# 6 способ делать вещи. Вся концепция "охраны" может быть заменена условным вызовом метода, и компилятор действительно делает правильную вещь (ТМ) в отношении захвата слушателей:

Tick?.Invoke(this, EventArgs.Empty);

Ответ 2

Microsoft фактически написала обширный набор принципов именования и поместила их в библиотеку MSDN. Вы можете найти статьи здесь: Рекомендации для имен

Помимо общих рекомендаций по капитализации, вот что он имеет для "События" на странице Имена членов типов:

Введите имена событий с помощью глагола или глагола Фраза.

Дайте именам событий концепции до и после, используя и прошедшее время. Например, закрыть событие, которое возникает перед окном закрывается, будем называть закрытием и тот, который поднят после того, как окно закрыто будет называться Закрыто.

Не используйте префиксы до или после или суффиксы, указывающие pre и post события.

Использовать обработчики событий (используются делегаты как типы событий) с Суффикс EventHandler.

Использовать два параметра с именем отправитель и e в сигнатурах обработчика событий.

Параметр отправителя должен иметь тип Объект и параметр e должны быть экземпляр или наследование от EventArgs.

Разделите классы аргументов событий суффикс EventArgs.

Ответ 3

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

Это соглашение, которое я принял, кратко:

  • Имена событий обычно заканчиваются глаголом, заканчивающимся на -ing или -ed (Закрытие/Закрытие, Загрузка/Загрузка)
  • Класс, объявляющий событие, должен иметь защищенный виртуальный On [EventName], который должен использоваться остальной частью класса для воссоздания события. Этот метод также может использоваться подклассами для создания события, а также для перегрузки для изменения логики создания событий.
  • Часто возникает проблема с использованием "Handler" - для согласованности все делегаты должны быть отправлены с помощью Handler, старайтесь избегать вызова методов, реализующих обработчики обработчика
  • Соглашение о присвоении имен по умолчанию для метода, реализующего обработчик, представляет собой событие EventPublisherName_EventName.

Ответ 5

Точка, которую я нашел после использования событий в .Net в течение многих лет, - это повторяющаяся необходимость проверять событие для нулевого обработчика при каждом вызове. Мне еще предстоит увидеть кусок живого кода, который ничего не делает, но не вызывает событие, если оно равно null.

То, что я начал делать, заключается в том, чтобы поместить обработчик фиктивных ям на каждое созданное событие, чтобы сохранить необходимость выполнить нулевую проверку.

public class Metronome
{
    public event EventHandler Tick =+ (s,e) => {};

    protected virtual void OnTick(EventArgs e)
    {
        Tick(this, e);  // now it safe to call without the null check.
    }
}

Ответ 6

Выглядит хорошо, кроме того, что OnTick не соответствует типичной модели вызова события. Как правило, On[EventName] вызывает событие за один раз, например

protected virtual void OnTick(EventArgs e)
{
    if(Tick != null) Tick(this, e);
}

Рассмотрите возможность создания этого метода и переименования существующего метода "OnTick" на "StartTick", и вместо вызова Tick непосредственно из StartTick вызовите OnTick(EventArgs.Empty) из метода StartTick.

Ответ 7

В вашем случае это может быть:

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);
  }

}

BTW. ключевое слово event является необязательным, но позволяет отличать "события" от "обратных вызовов"

Слушатель событий:

class AlarmManager {

  // pattern: NotifyXxx
  public NotifyLockStateChanged(bool state) {
    // ...
  }

  // pattern: [as above]      
  public NotifyOpened(bool opened) {
  // OR
  public NotifyDoorOpened(bool opened) {
    // ...
  }

}

И привязка [code look human friendly]

door.LockStateChanged += alarmManager.NotifyLockStateChanged;
door.Moved += alarmManager.NotifyDoorOpened;

Даже события, отправляемые вручную, являются "доступными для человека".

alarmManager.NotifyDoorOpened(true);

Иногда более выразительным может быть "глагол + ing"

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting;

Какое бы соглашение вы ни выбрали, согласитесь с ним.