如何正确注销事件处理程序 [英] How to correctly unregister an event handler

查看:226
本文介绍了如何正确注销事件处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在一个code检讨,我偶然发现了这个(简体)code片段注销事件处理程序:

In a code review, I stumbled over this (simplified) code fragment to unregister an event handler:

 Fire -= new MyDelegate(OnFire);

我觉得这不注销事件处理程序,因为它创造它以前从未注册的新代表。但搜索MSDN,我发现它用这个成语的几个code样本。

I thought that this does not unregister the event handler because it creates a new delegate which had never been registered before. But searching MSDN I found several code samples which use this idiom.

于是,我开始了一个实验:

So I started an experiment:

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);
    }

}

要我吃惊的是,下面的事情发生了:

To my surprise, the following happened:

  1. 火(你好1); 产生两个消息,如预期
  2. 火(你好2); 产生一个消息
    ! 这使我确信注销的代表作品!
  3. 火(你好3); 扔了的NullReferenceException
    。  调试code显示,取消注册事件之后。
  1. Fire("Hello 1"); produced two messages, as expected.
  2. Fire("Hello 2"); produced one message!
    This convinced me that unregistering new delegates works!
  3. Fire("Hello 3"); threw a NullReferenceException.
    Debugging the code showed that Fire is null after unregistering the event.

我知道,对事件处理程序和委托,编译器生成了大量的幕后code。但我还是不明白,为什么我的推理是错误的。

I know that for event handlers and delegate, the compiler generates a lot of code behind the scene. But I still don't understand why my reasoning is wrong.

我在想什么?

另一个问题:一个事实,即在没有注册的事件,我的结论是无处不在一个事件被解雇,针对检查是必需的。

Additional question: from the fact that Fire is null when there are no events registered, I conclude that everywhere an event is fired, a check against null is required.

推荐答案

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

The C# compiler's default implementation of adding an event handler calls Delegate.Combine, while removing an event handler calls Delegate.Remove:

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

该框架的实施 Delegate.Remove 不看 MyDelegate 对象本身,而在方法委托是指( Program.OnFire )。因此,它是绝对安全的退订现有的事件处理程序创建一个新的 MyDelegate 对象。正因为如此,C#编译器允许你使用简写语法(产生完全相同的code幕后)添加/删除事件处理程序时:你可以省略新MyDelegate 部分:

The Framework's implementation of Delegate.Remove doesn't look at the MyDelegate object itself, but at the method the delegate refers to (Program.OnFire). Thus, it's perfectly safe to create a new MyDelegate object when unsubscribing an existing event handler. Because of this, the C# compiler allows you to use a shorthand syntax (that generates exactly the same code behind the scenes) when adding/removing event handlers: you can omit the new MyDelegate part:

Fire += OnFire;
Fire -= OnFire;

在过去的委托从事件处理程序中删除, Delegate.Remove 返回null。正如你已经发现了,这是必须提高它之前要检查空事件:

When the last delegate is removed from the event handler, Delegate.Remove returns null. As you have found out, it's essential to check the event against null before raising it:

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

它分配给一个临时的局部变量,以防止可能的竞争条件与退订事件处理程序的其他线程防守。 (见我的博客文章的详细信息,分配事件处理程序的线程安全。一个局部变量)另一种方式来对这个问题的捍卫是创建一个总是订阅一个空的代表;而这使用多一点的内存,事件处理程序不能为空(和code可以更简单):

It's assigned to a temporary local variable to defend against a possible race condition with unsubscribing event handlers on other threads. (See my blog post for details on the thread safety of assigning the event handler to a local variable.) Another way to defend against this problem is to create an empty delegate that is always subscribed; while this uses a little more memory, the event handler can never be null (and the code can be simpler):

public static event MyDelegate Fire = delegate { };

这篇关于如何正确注销事件处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆