在调用自定义事件之前,为什么要检查null?

这两个用于调用事件的代码示例有什么区别?

样品1

public void OnDataChanged() { if (DataChanged != null) { DataChanged(this); } } 

样本2

 DataChanged.Invoke(this); 

我应该何时使用每种方法来调用自定义事件? 为什么有时我尝试使用DataChanged.Invoke(this)调用事件时会得到NullReferenceException,但是当我将事件调用转换为示例1中的方法时, DataChanged永远不会变为null?

OnXYZ方法应始终遵循以下forms:

 public void OnXYZ() { var evt = XYZ; if (evt != null) evt(sender, e); // where to get e from differs } 

这种forms有几个原因:

  1. if evt != null检查确保我们不会尝试调用null委托。 如果没有人将事件处理程序连接到事件,就会发生这种情况。
  2. 在multithreading场景中,由于委托是不可变的,一旦我们获得委托的本地副本到evt ,我们可以在检查非null之后安全地调用它,因为没有人可以在if但在调用之前改变它。

e传递的内容有所不同,如果需要使用参数传递EventArgs后代,有两种方法:

 public void OnXYZ(string p) { var evt = XYZ; if (evt != null) evt(sender, new SomeEventArgs(p)); } 

或者更常见的是:

 public void OnXYZ(SomeEventArgs e) { var evt = XYZ; if (evt != null) evt(sender, e); } 

这个语法:

 evt(sender, e); 

只是一种不同的写作方式:

 evt.Invoke(sender, e); 

另请注意,在您的类外部,事件是一个事件,您只能从中addremove事件处理程序。

在您的类的内部,事件是委托,您可以调用它,检查目标或方法,遍历订阅者列表等。


此外,在C#6中引入了一个新的运算符, ?. – Null条件运算符 – if not-null, dereference则基本上是short if not-null, dereference ,这可以缩短此方法:

 public void OnXYZ(SomeEventArgs e) { var evt = XYZ; if (evt != null) evt(sender, e); } 

进入这个:

 public void OnXYZ(SomeEventArgs e) { XYZ?.Invoke(sender, e); } 

使用Expression-bodied成员可以进一步缩短:

 public void OnXYZ(SomeEventArgs e) => XYZ?.Invoke(sender, e); 

请注意,不能写这个:

 XYZ?.(sender, e); 

所以在这种情况下你必须自己使用Invoke

当我将事件调用转换为示例1中的方法时,DataChanged永远不会变为空

然后你只是看两种不同的场景。

如果你没有声明像public event EventHandler YourEvent = delegate { };那样的public event EventHandler YourEvent = delegate { }; ,然后YourEventnull直到某些消费者订阅它。

如果没有订阅DataChanged它将被设置为null,所以当你尝试执行DataChanged.Invoke(this)时,你会得到一个NullRefException,因为它真的试图做null.Invoke(this)。 附加if(DataChanged!= null)的原因是为了避免在没有人订阅该事件时发生这种情况。

我不相信当你使用Sample 1 DataChanged永远不会为null时,它永远不会到达.Invoke来抛出exception。 如果没人订阅,它将始终为null。

您确定,在示例1中, DataChanged永远不会为空吗? 或者你只是没有得到NullReferenceexception(因为你在if语句中检查DataChanged 是否不为空 )?

让我们从基础开始。 活动是一种特殊的代表。 当你调用DataChanged(this)和DataChanged.Invoke(this)时,它是一样的。 为什么? 因为它编译成同样的东西。 总而言之, DataChanged(this)只是调用DataChanged.Invoke(this)的简写。

现在,为什么我们需要检查空引用(如示例1中所示)。 基本上,当您调用事件时,您将调用订阅此事件的所有方法(例如, DataChanged += someEventHandler )。 如果没有人订阅此事件,则它将具有null值。 没有分配方法来处理此事件。 换句话说:事件处理程序为null。

这就是为什么在调用事件之前检查null是一个好习惯。

一个例子:

 public void OnAbc(){ var data=Abc; if(!String.IsNullOrEmpty(data)) Abc(sender,e); }