对于扩展方法来说,这是个好主意吗?

本文关键字:好主意 于扩展 扩展 方法 | 更新日期: 2023-09-27 18:10:04

我经常在源代码周围看到这样的代码:

var handler = MyEvent;
if (handler != null)
{
    handler.Invoke(null, e);
}

有什么理由不把它封装在这样的扩展方法中吗?

public static void SafeInvoke<T>(this EventHandler<T> theEvent, object sender, T e) where T : EventArgs
{
    var handler = theEvent;
    if (handler != null)
    {
        handler.Invoke(sender, e);
    }
}

这样调用就可以像这样进行:

MyEvent.SafeInvoke(this, new MyEventArgs(myData));

对于扩展方法来说,这是个好主意吗?

这是一个有趣的问题。Jeffrey Richter通过c# 在CLR中谈了很多。

使用如下空检查:-

var handler = MyEvent;
if (handler != null)
{
    handler.Invoke(null, e);
}

JIT编译器可能会完全优化掉handler变量。然而,CLR团队意识到许多开发人员使用这种模式来引发事件,并将这种意识构建到JIT编译器中。这可能会在CLR的所有未来版本中保留,因为更改行为可能会破坏太多现有的应用程序。

没有理由你不能为你写一个扩展方法来完成这项工作(这正是我所做的)。请注意,方法本身可能是JIT内联的(但是临时变量仍然不会被优化掉)。

如果你设想你的应用程序被用于另一个运行时,例如Mono(我怀疑它在这种情况下反映了CLR的行为)或其他一些可能出现的奇异运行时,那么你可以通过在你的扩展方法中添加[MethodImplAttribute(MethodImplOptions.NoInlining)]来保护你的代码免受JIT内联的可能性。

如果您决定不使用扩展方法,另一种防止JIT优化的方法(Jeffrey Richter推荐)是以JIT编译器无法优化的方式将值赋给临时变量,例如

var handler = Interlocked.CompareExchange(ref MyEvent, null, null);

可能是出于性能考虑。

如果您不喜欢这种模式,您可以自由地创建这样的扩展方法。

我能看到的唯一缺点是增加了对扩展方法类所在的名称空间的依赖,并且其他开发人员将需要浪费3到5秒的时间来计算SafeInvoke的功能。