События - соглашение об именах и стиль
я узнаю о событиях / делегатах в 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;
какое бы соглашение вы ни выбрали, будьте в соответствии с ним.