为什么在调用自定义事件之前要检查是否为空?

本文关键字:是否 检查 调用 自定义 事件 为什么 | 更新日期: 2023-09-27 18:17:42

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

示例1

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

DataChanged.Invoke(this);

我应该在什么时候使用每个方法来调用自定义事件?为什么有时我得到一个NullReferenceException,当我试图调用事件使用DataChanged.Invoke(this),但当我转换事件调用的方法在样本1的DataChanged永远不会变成空了吗?

为什么在调用自定义事件之前要检查是否为空?

OnXYZ方法应始终遵循以下格式:

public void OnXYZ()
{
    var evt = XYZ;
    if (evt != null)
        evt(sender, e); // where to get e from differs
}
使用这种形式有以下几个原因:
  1. if evt != null检查确保我们不会尝试调用null委托。如果没有人将事件处理程序连接到事件,则可能发生这种情况。
  2. 在多线程场景中,由于委托是不可变的,一旦我们获得了委托到evt的本地副本,我们可以在检查非空后安全地调用它,因为没有人可以在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中引入了一个新的操作符,?.——空条件操作符——它基本上是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);
}

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

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

请注意不能这样写:

XYZ?.(sender, e);

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

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

那么你只是在看两种不同的场景。

如果你没有像public event EventHandler YourEvent = delegate { };那样声明一个事件,那么YourEvent就是null,直到有消费者订阅了它。

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

我不相信当你使用样本1时,datachchanged永远不会为空,它只是永远不会到达。invoke抛出异常。

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

让我们从基础开始。Event是一种特殊的委托。当您调用datachchanged (this)和datachchanged . invoke (this)时,它们是一样的。为什么?因为它编译成同样的东西。所以总而言之,DataChanged(this)只是调用DataChanged.Invoke(this)的简写。

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

这就是为什么在调用事件之前检查null是一个很好的实践。

示例:

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