ITPub博客

首页 > 应用开发 > C/C++ > 如何正确注销事件处理程序

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

C/C++ 作者:itchaindev 时间:2022-04-18 17:46:08 0 删除 编辑

 问题

在代码审查中,我偶然发现了这个(简化的)代码片段来取消注册事件处理程序:

 Fire -= new MyDelegate(OnFire);

我认为这不会取消注册事件处理程序,因为它会创建一个以前从未注册过的新委托。但是搜索 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代表是有效的!
  3. Fire("Hello 3");扔了一个 NullReferenceException。

    调试代码表明这 Fire是 null在取消注册事件之后。

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

我错过了什么?

附加问题:从没有注册事件的事实来看,我得出结论,无论 Fire何时 null触发事件, null都需要进行检查。

解决方案

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

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

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

Fire += OnFire;
Fire -= OnFire;

从事件处理程序中删除最后一个委托时, Delegate.Remove返回 null。正如您所发现的,在引发事件之前,必须针对 null 检查事件:

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

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

public static event MyDelegate Fire = delegate { };

文章来源: 如何正确注销事件处理程序


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70016198/viewspace-2887644/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2022-04-02

  • 博文量
    12
  • 访问量
    3420