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

я узнаю о событиях / делегатах в C#. Могу ли я спросить Ваше мнение о выбранном мной стиле именования / кодирования (взятом из первой книги на 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 ответов


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

метроном.OnTick, похоже, назван неправильно. Семантически " OnTick "говорит мне, что он будет называться, когда он"тик", но на самом деле это не то, что происходит. Я бы назвал это "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). Вы должны хотя бы включить null guard для проверки прослушивателей:

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. Здесь вы можете найти статьи: рекомендации для Имен

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

do name события с глаголом или глаголом фраза.

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

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

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

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

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

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


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

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

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

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

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


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

то, что я начал делать, - это поместить фиктивный обработчик в каждое событие, которое я создаю, чтобы сохранить необходимость проверки 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" метод "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;

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