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

时间:2015-11-26 04:08:28

标签: c# events null event-handling

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

SomeControl.Click += null;

没有害处。或者至少触发事件并不会通过尝试调用null事件侦听器来抛出异常(我可以告诉)。但是,Web上似乎没有任何地方可以验证我希望这样的代码至少不会做某些我可能不想做的事情。例如,导致Click事件认为它有可能没有侦听器,并在“现在让我们执行空检查并调用所有非空的侦听器”代码中浪费那些非常珍贵的超极小微秒。 / p>

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

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

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

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

2 个答案:

答案 0 :(得分:5)

考虑以下代码:

@ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
  @EJB
  RaffleManager roll;

  private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());

  private static void sendMessageToAll(String message) {
    for (Session s : sessions) {
      try {
        s.getBasicRemote().sendText(message);
      } catch (IOException ex) {
        ex.printStackTrace();
      }
    }
  }

  @OnOpen
  public void monitorLuckyDip(Session session) throws Exception {
    sessions.add(session);
    while(true) {
      sendMessageToAll(roll.getResult());
    }
  }
}

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

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;处理程序实际上被忽略。

就IL而言,行null产生以下IL:

foo.Blah += null;

因此,它表现为IL_001A: ldloc.0 // foo IL_001B: ldnull IL_001C: callvirt Foo.add_Blah IL_0021: nop ,但它清楚地运行代码。

答案 1 :(得分:4)

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

AddHandlers示例中,Button类可能将ClickLoad定义为事件,而不是代理。当其左操作数是一个事件时,+=运算符只调用事件的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类在&#34;标准&#34;中实现其事件。方式(而不是说,以明确的List<EventHandler>跟踪听众),然后button.Click += null;最终添加空的侦听器。