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

C# 为什么在调用自定义事件之前要检查null?,c#,events,C#,Events,调用事件的这两个代码示例之间有什么区别 样本1 public void OnDataChanged() { if (DataChanged != null) { DataChanged(this); } } 样本2 DataChanged.Invoke(this); 我应该在什么时候使用每个方法来调用自定义事件?为什么有时当我尝试使用DataChanged.invoke(this)调用事件时,会出现NullReferenceException,但当我将

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

样本1

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

DataChanged.Invoke(this);
我应该在什么时候使用每个方法来调用自定义事件?为什么有时当我尝试使用
DataChanged.invoke(this)
调用事件时,会出现NullReferenceException,但当我将事件调用转换为示例1中的方法时,
DataChanged
不再为null

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

然后,您只需查看两种不同的场景


如果不声明类似于
public event EventHandler的事件,那么YourEvent=delegate{}
,则
YourEvent
null
,直到有消费者订阅它。

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

public void OnXYZ()
{
    var evt = XYZ;
    if (evt != null)
        evt(sender, e); // where to get e from differs
}
这种形式有几个原因:

public void OnXYZ()
{
    var evt = XYZ;
    if (evt != null)
        evt(sender, e); // where to get e from differs
}
  • 如果evt!=null检查确保我们不会尝试调用
    null
    委托。如果没有人将事件处理程序连接到事件,则可能发生这种情况
  • 在多线程场景中,由于委托是不可变的,一旦我们在
    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);
    
    还要注意,在类的外部,事件是一个事件,您只能从中添加或删除事件处理程序

    在类内部,事件是一个委托,您可以调用它、检查目标或方法、遍历订阅服务器列表等


    此外,在C#6中引入了一个新的运算符,
    ?。
    -基本上是
    的缩写,如果不为null,则取消引用
    ,可以缩短此方法:

    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

    如果没有订阅数据更改,则会将其设置为null,因此当您尝试执行datachange时。Invoke(此)会得到一个NullRefException,因为它实际上是在尝试执行null.Invoke(此)。附加if(DataChanged!=null)的原因是为了避免在没有人订阅事件时发生这种情况


    我不相信当您使用示例1 DataChanged从不为null时,它永远不会到达.Invoke抛出异常。如果没有人订阅,它将始终为空。

    您确定在示例1中,
    数据更改
    从不为空吗?或者您只是没有得到NullReference异常(因为您在
    if
    语句中检查
    DataChanged
    是否不是null

    让我们从基础开始。事件是一种特殊的委托。当您调用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);
    }
    
    在C#6.0中,我建议使用:
    DataChanged?.Invoke(this)
    数据更改(此)
    只是
    DataChanged.Invoke(this)的简写他们做同样的事情。