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

a.t*_*aby 5 c# events

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

样品1

public void OnDataChanged()
{
    if (DataChanged != null)
    {
        DataChanged(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

样本2

DataChanged.Invoke(this);
Run Code Online (Sandbox Code Playgroud)

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

ang*_*son 15

一种OnXYZ方法应该始终遵循这种形式:

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

这种形式有几个原因:

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

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

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

或者更常见的是:

public void OnXYZ(SomeEventArgs e)
{
    var evt = XYZ;
    if (evt != null)
        evt(sender, e);
}
Run Code Online (Sandbox Code Playgroud)

这个语法:

evt(sender, e);
Run Code Online (Sandbox Code Playgroud)

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

evt.Invoke(sender, e);
Run Code Online (Sandbox Code Playgroud)

另请注意,在您的类外部,事件是一个事件,您只能addremove事件处理程序来自它.

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


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

public void OnXYZ(SomeEventArgs e)
{
    var evt = XYZ;
    if (evt != null)
        evt(sender, e);
}
Run Code Online (Sandbox Code Playgroud)

进入这个:

public void OnXYZ(SomeEventArgs e)
{
    XYZ?.Invoke(sender, e);
}
Run Code Online (Sandbox Code Playgroud)

可以通过使用表达身体成员进一步缩短:

public void OnXYZ(SomeEventArgs e) => XYZ?.Invoke(sender, e);
Run Code Online (Sandbox Code Playgroud)

请注意,不能写这个:

XYZ?.(sender, e);
Run Code Online (Sandbox Code Playgroud)

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