这是在 C# 中无需空检查即可触发/调用事件的更好方法吗?

本文关键字:事件 调用 更好 方法 检查 | 更新日期: 2023-09-27 18:31:57

我见过的大多数代码都使用以下方式来声明和调用事件触发:

public class MyExample
{
    public event Action MyEvent; // could be an event EventHandler<EventArgs>, too
    private void OnMyEvent()
    {
        var handler = this.MyEvent; // copy before access (to aviod race cond.)
        if (handler != null)
        {
            handler();
        }
    }
    public void DoSomeThingsAndFireEvent() 
    {
        // ... doing some things here
        OnMyEvent();
    }
 }

甚至ReSharper也以上述方式生成调用方法。

为什么不这样做:

public class MyExample
{
    public event Action MyEvent = delegate {}; // init here, so it's never null
    public void DoSomeThingsAndFireEvent() 
    {
        // ... doing some things here
        OnMyEvent(); // save to call directly because this can't be null
    }
 }

谁能解释为什么不这样做的原因?(优点与缺点)

这是在 C# 中无需空检查即可触发/调用事件的更好方法吗?

优点和缺点是:

  • 空支票非常便宜;我们说的是十亿分之一秒。分配一个委托,然后在对象的剩余生存期内对其进行垃圾回收失败,可能需要几百万分之一秒以上的时间。此外,您还会多消耗数十字节的内存。此外,每次触发事件时,您都会收到对不执行任何操作的方法的不必要调用,从而消耗更多的微秒。如果您是那种关心百万分之一秒和数十字节的人,那么这可能是一个有意义的差异;对于绝大多数情况,它不会。

  • 您必须记住始终创建空委托。这真的比记住检查空更容易吗?

  • 这两种模式实际上都没有使事件线程安全。在这两种模式中,事件处理程序仍然完全有可能在一个线程上触发,同时在另一个线程上被删除,这意味着它们会竞争。如果处理程序删除代码销毁处理程序所需的状态,则可能一个线程正在销毁该状态,而另一个线程正在运行处理程序。不要认为仅仅检查 null 或分配空处理程序就能神奇地消除争用条件。它仅消除了导致取消引用 null 的争用条件。

这肯定是一种风格; 但是,我认为大多数开发人员在涉及空检查时都追求安全而不是风格。 这个世界上没有什么可以保证错误不会蔓延到系统中,并且空检查将继续是不必要的。

它仍然可以为空。 考虑:

    var x = new MyExample();
    x.MyEvent += SomeHandler;
    // ... later, when the above code is disposed of
    x.MyEvent -= SomeHandler;

编辑:实际上,我把它收回来。 对此进行测试后,如果您使用匿名委托来设置处理程序,则看起来无法清除它,因此您可以保存 null 检查。

我不确定这是可靠的行为,还是只是语言实现的产物......

我见过的简化事件触发的最佳权衡是添加扩展方法。 请参阅使用扩展方法引发 C# 事件 - 它不好吗?。