添加(或删除)一个null事件侦听器是否为no-op



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

SomeControl.Click += null;

没有害处。或者,至少触发事件不会通过尝试调用null事件侦听器来抛出异常(我可以判断)。然而,在Web上似乎没有任何地方可以验证我的希望,即这样的代码至少没有做我可能不想做的事情。例如,让Click事件认为它有侦听器,而它可能没有,并在"现在让我们执行null检查并调用所有非null的侦听器"代码中浪费那些宝贵的超迷你微秒。

文档似乎应该说明如果RHS上的侦听器为null,那么+=和-=运算符的行为是什么。但正如我所说,我在任何地方都找不到那份文件。不过,我相信这里有人能提供。。。。

(显然,我的代码没有硬编码的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,但它显然运行代码。

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

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

button.add_Click(value);

与任何其他方法一样,add_Click如何处理null参数完全取决于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.

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

具有调用列表的新委托,该调用列表按顺序连接Ab的调用列表。如果bnull,则返回a;如果aa为null引用,则返回bb;如果ab均为null引用则返回null引用。

结论:只要Button类以"标准"方式实现其事件(而不是在显式List<EventHandler>中跟踪侦听器),那么button.Click += null;最终将而不是添加一个null侦听器。

相关内容

  • 没有找到相关文章

最新更新