【问题标题】:Events - naming convention and style事件 - 命名约定和风格
【发布时间】:2026-02-06 21:15:02
【问题描述】:

我正在学习 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

【问题讨论】:

    标签: c# events delegates


    【解决方案1】:

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

    除了一般的大写准则外,以下是页面Names of Type Members上的“事件”的内容:

    ✔️ 一定要用动词或动词短语来命名事件。

    示例包括ClickedPaintingDroppedDown 等。

    ✔️ 使用现在时和过去时给事件命名之前和之后的概念。

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

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

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

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

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

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

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

    【讨论】:

      【解决方案2】:

      我要提几点:

      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 的处理方式。整个“守卫”概念可以替换为条件方法调用,并且编译器在捕获侦听器方面确实做了正确的事情(TM):

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

      【讨论】:

      • 替代守卫是添加“=委托{};”勾选声明(见*.com/questions/231525/…
      • 请注意,该漏洞不限于多线程环境。事件处理程序有可能(如果反社会)从事件中删除所有处理程序,从而在处理程序完成并且事件调用尝试运行下一个(现在不存在的)事件时导致崩溃。
      • @GregD:有什么方法可以让客户端代码无法做到这一点?
      • @Kyle:嗯,我认为我的旧评论是不正确的。我不会出汗的。
      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

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

        见:Event Naming Guidelines (.NET Framework 1.1)

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          【解决方案6】:

          看起来不错,除了 OnTick 不遵循典型的事件调用模型。通常,On[EventName] 会引发一次事件,例如

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

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

          【讨论】:

            【解决方案7】:

            你的情况可能是:

            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;
            

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

            【讨论】: