在C#中引发事件:性能和优雅

本文关键字:性能 事件 | 更新日期: 2023-09-27 18:28:18

由于习惯了VB.NET,我习惯于"只引发事件"。当然,自定义事件有所不同,但对于"常规"事件,我不需要在引发之前检查委托是否为Nothing

使用C#,我发现自己在重复这个模式:

if (myHandler != null) 
{
    myHandler(this, new EventArgs());
}

我在想,以下模式可能会更优雅:

  1. myHandler是用空lambda:myHandler = (sender, e) => { };初始化的
  2. myHandler应该永远不会为null,因此引发将简单地变为:myHandler(this, new EventArgs());

这个模式会比上一个模式表现得更好还是更差?我是否还应该考虑其他重要因素?

在C#中引发事件:性能和优雅

在构建中会发生一些额外的事情,但不会产生巨大的开销。需要注意的一点是,一些序列化框架(如DataContractSerializer(WCF))不运行构造函数或字段初始化器,因此它可能不是非null的。就我个人而言,如果您的大多数事件都是EventHandler,我可能会想使用一种扩展方法:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if (handler != null) handler(sender, EventArgs.Empty);
}

然后:

SomeEvent.SafeInvoke(this);

尽管坦率地说,我很少对简单地使用null检查有问题;p

另一个缺点是,如果有足够多的事件表明这是一个问题,那么可能应该使用EventHandlerList,而这种方法不适用于EventHandlerList

常见的做法是使用受保护的虚拟方法OnEventName,在那里您可以检查事件是否为null并引发它:

protected virtual void OnEventName(parameters)
{
    if (EventName != null)
        EventName(this, new EventNameEventArgs(parameters);
}

这允许您将所有引发事件的代码放在一个位置(没有空检查重复),并在需要时稍后覆盖它。因此,我看不出为每个事件添加一个伪事件处理程序比为每个事件进行一次空检查有任何好处。

BTW添加伪处理程序的较短方法是myHandler = delegate {};

我认为两种方法之间的性能差异不够大,不相关。如果有什么不同的话,我认为null检查比通过委托调用方法便宜,即使它是no-op。

当谈到优雅时,应该指出的是,VB.NET中的RaiseEvent关键字由编译器自动扩展为与您必须在C#中编写的结构完全相同的结构:

If (Not MyEvent Is Nothing) Then
  MyEvent.Invoke(New EventArgs())
End If

如果你想避免在整个代码中重复这个构造,你可以用几个扩展方法来封装它:

public static void RaiseEvent(this EventHandler source, object sender)
{
    if (source != null)
    {
        source.Invoke(sender, new EventArgs());
    }
}
public static void RaiseEvent<T>(this EventHandler<T> source, object sender, T eventArgs)
    where T : EventArgs
{
    if (source != null)
    {
        source.Invoke(sender, eventArgs);
    }
}

这样你就可以简单地说:

myEvent.RaiseEvent(this);
myOtherEvent.RaiseEvent(this, new SomeEventArgs());

它在语义上等同于VB.NET中使用的样式。

不要认为提供的第一种和第二种情况之间有显著差异,至少不要考虑太多。在上,过于频繁地使用委派而不使用if (myHandler != null)可能会给您带来一些性能优势。因此,如果你确定处理程序是nevernull,那么就去掉这个控制,基本上就完成了。