添加(或删除)null事件监听器是否为no-op?

Man*_*and 7 c# null events event-handling

通常可以在这里找到这样的问题的答案,或者如果不是在这里,可以在MSDN上找到,但我看了看,但没有找到答案.实验似乎表明代码如下:

SomeControl.Click += null;
Run Code Online (Sandbox Code Playgroud)

没有害处.或者至少触发事件并不会通过尝试调用null事件侦听器来抛出异常(我可以告诉).然而,似乎无处将验证我希望,这样的代码是不是做至少在网络上的东西,我可能不想做.例如,导致Click事件认为它有听众,当它可能没有时,并且在"现在让我们执行空检查并调用所有非空的侦听器"代码中浪费那些非常珍贵的超超微秒.

似乎文档应该说明如果RHS上的监听器为空,+ =和 - =运算符的行为是什么.但就像我说的那样,我无法在任何地方找到该文档.我相信这里有人可以提供它,但....

(显然,我的代码没有硬编码的null;我的问题更多的是关于这样的代码是浪费还是完全无害:

public static void AddHandlers([NotNull] this Button button,
                               [CanBeNull] EventHandler click = null,
                               [CanBeNull] EventHandler load = null)
{
    button.Click += click;
    button.Load += load;
}
Run Code Online (Sandbox Code Playgroud)

或者如果我[ 因为任何原因需要 ]在每个这样的+ =操作周围添加空检查.)

Eni*_*ity 5

考虑以下代码:

void Main()
{
    var foo = new Foo();
    foo.Blah += Qaz;
    foo.Blah += null;
    foo.OnBlah();
}

public void Qaz()
{
    Console.WriteLine("Qaz");
}

public class Foo
{
    public event Action Blah;
    public void OnBlah()
    {
        var b = Blah;
        if (b != null)
        {
            Console.WriteLine("Calling Blah");
            b();
            Console.WriteLine("Called Blah");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

因为它运行没有错误并产生以下输出:

Calling Blah
Qaz
Called Blah
Run Code Online (Sandbox Code Playgroud)

如果我删除该行,foo.Blah += Qaz;那么代码运行没有错误,但不产生输出,因此null有效地忽略了处理程序.

就IL而言,该行foo.Blah += null;产生以下IL:

IL_001A:  ldloc.0     // foo
IL_001B:  ldnull      
IL_001C:  callvirt    Foo.add_Blah
IL_0021:  nop 
Run Code Online (Sandbox Code Playgroud)

所以,它表现得像nop,但它清楚地运行代码.


Mic*_*Liu 5

答案取决于+=(or -=) 运算符是应用于事件还是委托

在您的AddHandlers示例中,Button该类可能将Click和定义Load为事件,而不是委托。当它的左操作数是一个事件时,+=运算符只需调用add事件的访问器,而无需任何额外的空检查。也就是说,在幕后button.Click += value;只是一个方法调用:

button.add_Click(value);
Run Code Online (Sandbox Code Playgroud)

与任何其他方法一样,如何add_Click处理空参数完全取决于Button类。

现在考虑Button该类如何实际实现该Click事件。这是一种方法:

private EventHandler _click;
public event EventHandler Click
{
    add { _click += value; }
    remove { _click -= value; }
}

// Or even shorter...
// public event EventHandler Click;
// ... which is the same as above plus extra stuff for thread safety.
Run Code Online (Sandbox Code Playgroud)

在这个实现中,EventHandler是一个委托类型。当两个操作数都是委托时,+=运算符调用Delegate.Combine. 文档说Delegate.Combine(a, b)返回:

具有调用列表的新委托,该调用列表按该顺序连接ab的调用列表。返回一个,如果b,返回b,如果一个是空引用,并返回如果两个空引用一个b是空引用。

结论:只要Button该类以“标准”方式实现其事件(而不是,例如,以显式方式跟踪侦听器List<EventHandler>),则button.Click += null;最终不会添加空侦听器。