繁体   English   中英

事件 - 命名约定和样式

[英]Events - naming convention and style

我正在学习 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");
        }
    }

nb 代码重构自http://www.codeproject.com/KB/cs/simplesteventexample.aspx

Microsoft 实际上已经编写了大量的命名指南并将其放入 MSDN 库中。 您可以在此处找到这些文章: 命名指南

除了一般的大写准则外,以下是类型成员名称页面上的“事件”:

✔️ 务必用动词或动词短语命名事件。

示例包括ClickedPaintingDroppedDown等。

✔️ 务必使用前后概念,使用现在时和过去时为事件命名。

例如,在窗口关闭之前引发的关闭事件将称为Closing ,而在窗口关闭之后引发的事件将称为Closed

❌ 不要使用“Before”或“After”前缀或后缀来表示前后事件。 使用刚才描述的现在和过去时。

✔️ 务必使用“EventHandler”后缀命名事件处理程序(用作事件类型的委托),如下例所示:

 public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);

✔️ 务必在事件处理程序中使用两个名为sendere参数。

sender 参数表示引发事件的对象。 sender 参数通常是object类型,即使可以使用更具体的类型。

✔️ 务必使用“EventArgs”后缀命名事件参数类。

有几点我想提一下:

Metronome.OnTick 似乎没有正确命名。 从语义上讲,“OnTick”告诉我它会在“Tick”s 时被调用,但这并不是真正发生的事情。 我会称它为“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);

我知道这是一个旧答案,但由于它仍在收集投票,​​这里是 C# 6 的做事方式。 整个“守卫”概念可以用条件方法调用代替,并且编译器确实在捕获侦听器方面做了正确的事情(TM):

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

我会说一般事件的最佳指南,包括命名约定,在这里

这是我采用的约定,简要地说:

  • 事件名称通常以 -ing 或 -ed 结尾的动词结尾(Closing/Closed, Loading/Loaded)
  • 声明事件的类应该有一个受保护的虚拟 On[EventName],类的其余部分应该使用它来引发事件。 这个方法也可以被子类用来引发事件,也可以重载来修改事件引发逻辑。
  • 'Handler' 的使用经常存在混淆 - 为了连贯性,所有委托都应该后缀为 Handler,尽量避免调用实现 Handler 'handlers' 的方法
  • 实现处理程序的方法的默认 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 ”,而不是直接从StartTick调用Tick ,而是从StartTick方法调用OnTick(EventArgs.Empty)

在你的情况下,它可能是:

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

有时更具表现力的可以是“动词 + ing”

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting;

无论您选择哪种约定,都要与其保持一致。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM