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


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

n. B. код рефакторируется из http://www.codeproject.com/KB/cs/simplesteventexample.aspx

7 54

7 ответов:

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

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

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

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

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

Microsoft фактически написала обширный набор рекомендаций по именованию и поместила его в библиотеку MSDN. Здесь вы можете найти статьи: рекомендации для Имен

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

назовите события с помощью глагола или глагола фраза.

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

не использовать до или после префиксов или суффиксы для обозначения pre и post события.

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

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

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

назовите классы аргументов событий с помощью суффикс EventArgs.

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

Это конвенция, которую я принял, кратко:

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

интересно, как Microsoft, похоже, нарушает свои собственные соглашения об именах с именами обработчиков событий, созданных Visual Studio.

посмотреть: рекомендации по именованию событий (.NET Framework 1.1)

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

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

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 "метод to"StartTick", и вместо вызова Tick непосредственно с StartTick, называют OnTick(EventArgs.Empty) С StartTick метод.

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

class Metronome {
  event Action Ticked;

  internalMethod() {
    // bla bla
    Ticked();
  }
}

выше sampple использовать ниже конвенции, самоописания ;]

событий Источник:

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

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

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting;

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