添加(或删除)空事件侦听器是空操作吗?

Is adding (or removing) a null event listener a no-op?

通常可以在这里找到此类问题的答案,如果不在这里,也可以在 MSDN 上找到,但我看了又看,还是没有找到答案。实验似乎表明代码如下:

SomeControl.Click += null;

没有坏处。或者至少触发事件不会通过尝试调用空事件侦听器来抛出异常(我可以告诉)。但是,Web 上似乎没有任何地方可以证实我的希望,即这样的代码至少没有做 一些我可能不想做的事情。例如,导致 Click 事件认为它有侦听器,而实际上它可能没有,并在 "now let's perform null-checks and invoke all listeners that are not null" 代码中浪费了那些非常宝贵的超微秒。

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

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

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

或者如果我应该[即,需要出于任何原因]在每个这样的 += 操作周围添加空值检查。)

考虑这段代码:

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");
        }
    }
}

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

Calling Blah
Qaz
Called Blah

如果我删除行 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 

因此,它的行为与 nop 相同,但它清楚地运行了代码。

答案取决于 +=(或 -=)运算符是否应用于 eventdelegate.

在您的 AddHandlers 示例中,Button class 可能将 ClickLoad 定义为事件,而不是代表。当它的左操作数是一个事件时,+= 运算符只调用事件的 add 访问器,而不进行任何额外的 null 检查。也就是说,在幕后 button.Click += value; 只是一个方法调用:

button.add_Click(value);

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

现在考虑 Button class 如何实际执行 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.

在此实现中,EventHandler is a delegate type. When both operands are delegates, the += operator invokes Delegate.Combine。文档说 Delegate.Combine(a, b) returns:

A new delegate with an invocation list that concatenates the invocation lists of a and b in that order. Returns a if b is null, returns b if a is a null reference, and returns a null reference if both a and b are null references.

结论:只要 Button class 以 "standard" 方式实现其事件(而不是,比如说,以显式 List<EventHandler> 跟踪听众),那么 button.Click += null; 最终将 添加空侦听器。