【问题标题】:Is adding (or removing) a null event listener a no-op?添加(或删除)空事件侦听器是无操作吗?
【发布时间】:2015-11-26 04:08:28
【问题描述】:

通常可以在此处找到此类问题的答案,或者,如果没有,请在 MSDN 上找到,但我看了又看,没有找到答案。实验似乎表明代码如下:

SomeControl.Click += null;

没有害处。或者至少触发事件不会通过尝试调用空事件侦听器来抛出异常(我可以说)。但是,Web 上似乎没有任何地方可以验证我希望这样的代码没有做至少 一些我可能不想做的事情。例如,导致 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;
}

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

【问题讨论】:

    标签: c# events null event-handling


    【解决方案1】:

    考虑这段代码:

    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,但它显然会运行代码。

    【讨论】:

    • 谢谢!我应该考虑尝试的!
    【解决方案2】:

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

    在您的 AddHandlers 示例中,Button 类可能将 Click 和 Load 定义为事件,而不是委托。当其左操作数是事件时,+= 运算符只需调用事件的add 访问器,无需任何额外的空检查。也就是说,在幕后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) 返回:

    具有调用列表的新委托,该列表按顺序连接 a 和 b 的调用列表。如果 b 为 null,则返回 a,如果 a 为空引用,则返回 b , 如果 a 和 b 都是空引用,则返回空引用。

    结论:只要Button 类以“标准”方式实现其事件(而不是,比如说,以明确的List<EventHandler> 跟踪侦听器),那么button.Click += null; 最终不会 添加一个空监听器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-18
      • 2013-06-27
      • 1970-01-01
      • 2018-09-04
      • 1970-01-01
      • 2020-06-29
      • 2018-04-26
      相关资源
      最近更新 更多