【问题标题】:Create empty C# event handlers automatically自动创建空的 C# 事件处理程序
【发布时间】:2010-09-25 07:58:39
【问题描述】:

不可能在 C# 中触发没有附加处理程序的事件。所以每次调用前都要检查事件是否为空。

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

我想让我的代码尽可能干净,并摆脱那些空检查。我认为它不会对性能产生太大影响,至少对我而言不会。

MyEvent( param1, param2 );

现在我通过手动为每个事件添加一个空的内联处理程序来解决这个问题。这很容易出错,因为我需要记住这样做等等。

void Initialize() {
  MyEvent += new MyEvent( (p1,p2) => { } );
}

有没有办法使用反射和一些 CLR 魔法自动为给定类的所有事件生成空处理程序?

【问题讨论】:

标签: c# events delegates clr


【解决方案1】:

我在另一个帖子上看到了这个,然后无耻地窃取了它并在我的大部分代码中使用它:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

//Let you do this:
public void DoSomething() {
    Click(this, "foo");
}

//Instead of this:
public void DoSomething() {
    if (Click != null) // Unnecessary!
        Click(this, "foo");
}

* 如果有人知道这种技术的起源,请在 cmets 中发布。我真的相信消息来源会得到应有的赞誉。

编辑:我从这篇帖子Hidden Features of C#?得到它)

【讨论】:

  • 添加空委托,就在那儿!这比我希望的还要好。谢谢!我现在要阅读“隐藏功能”的帖子。
  • 是的——那篇文章是无价的!一定要经常在那里投票。他们为我们所有人提供了很好的服务。
  • -1 有两个原因:1) 这种技术会导致运行时性能和内存开销;2) 这种技术容易出错,尤其是与下面描述的扩展方法相比。仅查看调用站点不足以确定此方法的正确性,但扩展方法适用于所有事件,无论事件是否使用空委托进行初始化。
  • 值得注意的是,使用空委托进行初始化仅适用于class,不适用于struct
【解决方案2】:

你可以这样写:

MyEvent += delegate { };

我不确定你想要做什么是正确的。

【讨论】:

  • 我真的相信,向每个事件添加空委托是正确的方法,如何开发应用程序。但我相信,可能会有这样的情况,即如何快速简单地解决问题。
【解决方案3】:

符号:

if ( MyEvent != null ) {
  MyEvent( param1, param2 );
}

不是线程安全的。你应该这样做:

EventHandler handler = this.MyEvent;
if ( null != handler ) { handler( param1, param2 ); }

我明白了,这很麻烦,所以你可以做辅助方法:

static void RaiseEvent( EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

然后调用:

RaiseEvent( MyEvent, param1, param2 );

如果您使用的是 C# 3.0,则可以将辅助方法声明为扩展方法:

static void Raise( this EventHandler handler, object sender, EventArgs e ) {
    if ( null != handler ) { handler( sender, e ); }
}

然后调用:

MyEvent.Raise( param1, param2 );

您还可以为其他事件处理程序创建下一个扩展/帮助方法。例如:

static void Raise<TEventArgs>( this EventHandler<TEventArgs> handler,
    object sender, TEventArgs e ) where TEventArgs : EventArgs
{
    if ( null != handler ) { handler( sender, e ); }
}

【讨论】:

  • 使用扩展方法是一个很好的解决方案。当提出初始化空委托的概念时,我感到畏缩。
  • 哇,当我第一次看到= delegate {} 时,我觉得它很方便。不过,这太棒了+1。事后看来如此明显,该死的:)
  • handler?.Invoke(sender, e)
【解决方案4】:

这是一个坏主意,因为使用该事件的代码现在期望带有该事件的对象已默认使用操作进行编码。如果您的代码永远不会被其他任何人使用,那么我想您可以侥幸逃脱。

【讨论】:

  • 我同意,因为我对 leppie 的回答发表了评论。 +1
【解决方案5】:

您不需要为不同的事件处理程序提供多种扩展方法,您只需要一种:

public static class EventHandlerExtensions {
  public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs {
    if (handler != null) handler(sender, args);
  }
}

【讨论】:

    【解决方案6】:

    您可以使用 PostSharp 在构建时添加这个魔法。这是最好的方法。

    【讨论】:

      【解决方案7】:

      不幸的是,C# 事件声明包含许多众所周知的安全问题和低效率。 I designed a number of extension methods on delegates to invoke them safely, and to register/unregister delegates in a thread-safe manner.

      你的旧事件引发代码:

      if (someDelegate != null) someDelegate(x, y, z);
      

      您的新代码:

      someDelegate.Raise(x, y, z);
      

      您的旧活动注册码:

      event Action fooEvent;
      ...
      lock (someDummyObject) fooEvent += newHandler;
      

      您的新代码:

      Action fooEvent;
      ...
      Events.Add(ref fooEvent, newHandler);
      

      不需要锁定,也不需要编译器插入的用于锁定事件的虚拟对象。

      【讨论】:

        【解决方案8】:

        在 C# 6.0 中,由于条件空运算符 ?.

        The docs 说明调用 MyEvent?.Invoke(...) 会将事件复制到临时变量,执行 null 检查,如果不为 null,则在临时副本上调用 Invoke。这不一定在任何意义上都是线程安全的,因为有人可能在复制到临时变量之后添加了一个新事件,而该临时变量不会被调用。它确实保证您不会在 null 上调用 Invoke

        简而言之:

        public delegate void MyClickHandler(object sender, string myValue);
        public event MyClickHandler Click;
        
        public void DoSomething() {
            Click?.Invoke(this, "foo");
        }
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-08-15
        • 1970-01-01
        • 2013-12-24
        • 1970-01-01
        • 2019-04-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多