自动创建空的C#事件处理程序


71

无法在C#中触发没有附加处理程序的事件。因此,在每次调用之前,有必要检查事件是否为null。

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

我想保持我的代码尽可能干净,并摆脱那些空检查。我认为这不会对性能产生太大影响,至少在我看来不会如此。

MyEvent( param1, param2 );

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

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

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


接受的答案中的技巧将避免避免检查null,但不能确保线程安全。在这里看到:stackoverflow.com/questions/1131184/...

Answers:


154

从那以后,我在另一篇文章中看到了这一点,并从此无耻地偷走了它,并在我的许多代码中使用了它:

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

*如果有人知道此技术的起源,请在评论中发布。我确实相信来源会得到应有的重视。

编辑:我从这篇文章中得到了C#的隐藏功能?


3
在此处添加空的代表!甚至比我期望的要好。谢谢!我现在要阅读“隐藏功能”文章。
Tomas Andrle

是的-该帖子无价!确保在那里经常投票。他们为我们所有人提供了出色的服务。
黛娜

6
-1的原因有两个:1)此技术会同时导致运行时性能和内存开销,并且2)此技术容易出错,特别是与以下描述的扩展方法相比。仅查看调用站点不足以确定此方法的正确性,但是扩展方法适用于所有事件,无论该事件是否使用空委托初始化。
Sam Harwell

值得注意的是,使用空委托进行初始化仅适用于class,不适用于struct
sebrockm

60

表示法:

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,则可以将helper方法声明为扩展方法:

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

4
使用扩展方法是一个很好的解决方案。当提出初始化空代理的概念时,我感到不满。
格雷格

1
哇,= delegate {}当我第一次看到它时,我以为它很方便。不过,这很棒+1。事后看来很明显,该死的:)
anton.burger

处理程序?。调用(发送方,e)
Michael20年

10

在C#6.0中,由于有条件的null运算符,因此无需进行任何长度的检查即可进行null检查。 ?.

文档解释说,调用MyEvent?.Invoke(...)会将事件复制到临时变量,执行null检查,如果不为null,则调用Invoke临时副本。从某种意义上说,这不一定是线程安全的,因为有人可以在将副本复制到临时变量之后添加一个新事件,而该事件不会被调用。它确实保证您不会调用Invokenull。

简而言之:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click;

public void DoSomething() {
    Click?.Invoke(this, "foo");
}

3
我也将添加到这一点,性能差异MyEvent?.Invoke(...)MyEvent(...)一个空的代表是根据我的测试显著:在.?比空的委托方法快大约〜40%。有关我用来测试此代码的代码,请参见gist.github.com/didii/c4e8ef021fb8b9fca7898d71eb0de79a
Didii

6

您可以这样写:

MyEvent += delegate { };

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


我真的相信,向每个事件添加空委托都是正确的方式,即如何开发应用程序。但我相信,在某些情况下,如何处理某些问题的快速简便的解决方案是存在的。
TcK

6

对于不同的事件处理程序,您不需要几种扩展方法,只需要一种:

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

2

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


我同意对嬉皮士的回答的评论。+1
TcK's

1

不幸的是,C#事件声明包括许多众所周知的安全问题和效率低下的问题。我在委托上设计了许多扩展方法,以安全地调用它们,并以线程安全的方式注册/注销委托

您的旧的引发事件的代码:

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

无需锁定,也无需使用插入编译器的伪对象来锁定事件。


-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.