这是一个好的扩展方法的想法吗?

4

我经常看到像这样的代码在源代码中分散复制:

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)); 该技术与IT相关。

当然,这就是函数的作用;) - NiematojakTomasz
嗯...好的。我之前从未见过这个扩展方法,所以想知道为什么人们总是复制代码而不是使用它。 - Ryan Peschel
5
可能是因为这个问题很琐碎并且永远不会改变,所以重复的唯一“糟糕”之处就是额外的代码。你也可以在声明时将事件设置为空委托,这样就不需要进行空值检查了,即public event SomeEventHandler Foo = delegate { }; - Ed S.
2
@EdS.:查看此帖子的评论以了解为什么这是个坏主意。 - Ryan Peschel
@Ryan:我不相信“如果您不触发事件,会增加开销”的说法,因为如果我不想触发事件,我就会将其删除!然而,这是一个有趣的观点,我不知道这是真的(大多数人并不编写公共API,担心破坏接口)。 这也显示了我多么频繁地序列化我的类,因为我从未遇到过这个问题。虽然如此,知识总是好的,我不知道JIT'er对于分配+空检查的线程安全有特别的情况,所有这些都是合理的观点,所以谢谢! - Ed S.
3个回答

3
这是一个有趣的问题。Jeffrey Richter在《CLR via C#》中详细谈到了这个问题。
使用以下代码进行空值检查:
var handler = MyEvent;

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

JIT编译器有可能会完全优化掉“handler”变量。然而,CLR团队意识到许多开发人员使用这种模式来引发事件,并将这种意识融入了JIT编译器中。由于更改行为可能会破坏太多现有应用程序,因此这很可能会在所有未来的CLR版本中保持不变。
您可以编写一个扩展方法来完成这项工作(这正是我所做的)。请注意,该方法本身可能会被JIT内联(但临时变量仍不会被优化掉)。
如果您预计您的应用程序将针对另一个运行时使用,例如Mono(我怀疑其在这种情况下反映CLR的行为)或其他可能出现的奇特运行时,则可以通过在扩展方法中添加[MethodImplAttribute(MethodImplOptions.NoInlining)]来防止JIT内联的可能性。
如果您决定不使用扩展方法,则可以像Jeffrey Richter建议的那样以不能被JIT编译器优化掉的方式将值分配给临时变量,例如:
var handler = Interlocked.CompareExchange(ref MyEvent, null, null);

0

在文章结尾处,扩展方法的执行时间要么与普通方法相同,要么更快(可能是因为缓存)。空委托是唯一存在性能问题的方法,我不会使用它。 - Ryan Peschel
我并没有说有问题,只是需要考虑一下 :)!而且,唯一的问题就是空委托。 - Panos

0

如果这个模式让你感到困扰,你可以自由地创建这样的扩展方法。

我唯一能看到的缺点是增加了对扩展方法类所在命名空间的依赖,以及其他开发人员需要浪费3到5秒钟的时间来理解SafeInvoke的作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接