事件 - 命名约定和样式

Dav*_*eer 58 c# events delegates

我正在学习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();
        }
    }
Run Code Online (Sandbox Code Playgroud)
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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)
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");
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

Yot*_*aXP 65

Microsoft实际上编写了大量的命名准则并将其放在MSDN库中.你可以在这里找到文章:名称指南

除了一般的大写指南之外,以下是它在" 名称类型成员 "页面上对"事件"的含义:

使用动词或动词短语命名事件.

使用现在和过去时,给事件名称赋予前后概念.例如,在关闭窗口之前引发的close事件将被称为Closing,窗口关闭后引发的close事件将被称为Closed.

请勿使用Before或After前缀或后缀来指示事件前和事件后.

使用EventHandler后缀命名事件处理程序(用作事件类型的委托).

在事件处理程序签名中使用名为sender和e的两个参数.

sender参数应为Object类型,e参数应为EventArgs的实例或从EventArgs继承.

使用EventArgs后缀命名事件参数类.


lc.*_*lc. 53

我要提几点:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,我知道这是一个简单的例子,但如果没有附加的监听器,你的代码就会抛出Tick(this, EventArgs.Empty).你应该至少包括一个空值守卫来检查听众:

if(Tick != null)
    Tick(this, EventArgs.Empty);
Run Code Online (Sandbox Code Playgroud)

但是,如果侦听器在调用和调用之间取消注册,则在多线程环境中仍然容易受到攻击.最好的方法是首先捕获当前的侦听器并调用它们:

var tickEvent = Tick;
if(tickEvent != null)
    tickEvent(this, EventArgs.Empty);
Run Code Online (Sandbox Code Playgroud)

我知道这是一个古老的答案,但由于它仍在收集投票,​​这里是C#6的做事方式.整个"guard"概念可以用条件方法调用替换,编译器确实在捕获侦听器方面做了Right Thing(TM):

Tick?.Invoke(this, EventArgs.Empty);
Run Code Online (Sandbox Code Playgroud)

  • 警卫的替代方案是添加"= delegate {};" 到Tick声明(见http://stackoverflow.com/questions/231525/raising-c-events-with-an-extension-method-is-it-bad/231536#231536) (14认同)
  • @Kyle:嗯,我认为我的旧评论不正确.我不会流汗. (3认同)

Ben*_*jol 16

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

这是我采用的惯例,简要说明:

  • 事件名称通常以动词以-ing或-ed结尾(闭合/闭合,加载/加载)结束
  • 声明事件的类应该有一个受保护的虚拟On [EventName],该类的其余部分应该使用它来引发事件.子类也可以使用此方法来引发事件,并且还可以重载此方法以修改事件引发逻辑.
  • 关于使用'Handler'经常会产生混淆 - 为了保持连贯性,所有代表都应该使用Handler作为后缀,尽量避免调用实现Handler'处理程序'的方法
  • 实现处理程序的方法的默认VS命名约定是EventPublisherName_EventName.


小智 7

有趣的是,微软似乎打破了自己的命名约定,使用Visual Studio生成的事件处理程序名称.

请参阅:事件命名准则(.NET Framework 1.1)