gpt4 book ai didi

c# - 如何正确注销事件处理程序

转载 作者:IT王子 更新时间:2023-10-29 03:43:47 25 4
gpt4 key购买 nike

在一次代码审查中,我偶然发现了这个(简化的)代码片段以注销事件处理程序:

 Fire -= new MyDelegate(OnFire);

我认为这不会注销事件处理程序,因为它创建了一个以前从未注册过的新委托(delegate)。但是搜索 MSDN 我发现了几个使用这个习惯用法的代码示例。

所以我开始了一个实验:

internal class Program
{
public delegate void MyDelegate(string msg);
public static event MyDelegate Fire;

private static void Main(string[] args)
{
Fire += new MyDelegate(OnFire);
Fire += new MyDelegate(OnFire);
Fire("Hello 1");
Fire -= new MyDelegate(OnFire);
Fire("Hello 2");
Fire -= new MyDelegate(OnFire);
Fire("Hello 3");
}

private static void OnFire(string msg)
{
Console.WriteLine("OnFire: {0}", msg);
}

}

令我惊讶的是,发生了以下情况:

  1. Fire("Hello 1"); 按预期生成了两条消息。
  2. Fire("Hello 2"); 产生了一条消息!
    这让我确信注销 new 委托(delegate)是有效的!
  3. Fire("Hello 3"); 抛出一个 NullReferenceException
    调试代码表明,取消注册事件后 Firenull

我知道对于事件处理程序和委托(delegate),编译器会在后台生成大量代码。但是我还是不明白为什么我的推理是错误的。

我错过了什么?

附加问题:根据当没有事件注册时 Firenull 的事实,我得出结论,无论在何处触发事件,都会对 null 进行检查 是必需的。

最佳答案

C# 编译器添加事件处理程序的默认实现调用 Delegate.Combine,而删除事件处理程序调用 Delegate.Remove:

Fire = (MyDelegate) Delegate.Remove(Fire, new MyDelegate(Program.OnFire));

Delegate.Remove 的框架实现不查看 MyDelegate 对象本身,而是查看委托(delegate)引用的方法 (Program.OnFire)。因此,在取消订阅现有事件处理程序时创建新的 MyDelegate 对象是绝对安全的。因此,C# 编译器允许您在添加/删除事件处理程序时使用速记语法(在幕后生成完全相同的代码):您可以省略 new MyDelegate 部分:

Fire += OnFire;
Fire -= OnFire;

当最后一个委托(delegate)从事件处理程序中删除时,Delegate.Remove 返回 null。正如您所发现的,在引发事件之前检查事件是否为 null 非常重要:

MyDelegate handler = Fire;
if (handler != null)
handler("Hello 3");

它被分配给一个临时局部变量,以防止在其他线程上取消订阅事件处理程序时可能出现的竞争条件。 (有关将事件处理程序分配给局部变量的线程安全性的详细信息,请参见 my blog post。)另一种防止此问题的方法是创建一个始终被订阅的空委托(delegate);虽然这会使用更多内存,但事件处理程序永远不能为 null(并且代码可以更简单):

public static event MyDelegate Fire = delegate { };

关于c# - 如何正确注销事件处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/292820/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com