事件 - 命名约定和样式

时间:2009-04-07 03:59:02

标签: 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();
        }
    }

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 个答案:

答案 0 :(得分:64)

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

除了一般大写指南之外,以下是Names of Type Members页面上“活动”的内容:

  

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

     

给事件名称一个概念   使用现在之前和之后   和过去时。例如,关闭   在窗口之前引发的事件   关闭将被称为关闭和   在窗口之后被抬起的那个   关闭将被称为关闭。

     

请勿使用Before或After前缀或   后缀表示前后   事件

     

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

     

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

     

sender参数应为type   对象和e参数应该是   一个实例或继承自   EventArgs的。

     

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

答案 1 :(得分: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
        }
    }
}

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

if(Tick != null)
    Tick(this, EventArgs.Empty);

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

var tickEvent = Tick;
if(tickEvent != null)
    tickEvent(this, EventArgs.Empty);

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

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

答案 2 :(得分:15)

我想说一般事件的最佳指南,包括命名约定,是here

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

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

答案 3 :(得分:7)

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

请参阅:Event Naming Guidelines (.NET Framework 1.1)

答案 4 :(得分:4)

我在.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.
    }
}

答案 5 :(得分:2)

除了OnTick不遵循典型的事件调用模型这一事实外,看起来不错。通常情况下,On[EventName]会一次性提升事件,例如

protected virtual void OnTick(EventArgs e)
{
    if(Tick != null) Tick(this, e);
}

考虑创建此方法,并将现有的“OnTick”方法重命名为“StartTick”,而不是直接从Tick调用StartTick,请致电{{1来自OnTick(EventArgs.Empty)方法。

答案 6 :(得分:2)

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

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;

无论您选择哪种惯例,都要与之保持一致。