如何正确注销事件处理程序 [英] How to correctly unregister an event handler
问题描述
在代码审查中,我绊倒了这个(简化的)代码段,以取消注册事件处理程序:
In a code review, I stumbled over this (simplified) code fragment to unregister an event handler:
Fire -= new MyDelegate(OnFire);
我以为这不会注销事件处理程序,因为它创建了一个从未被注册的新代理之前。但是搜索MSDN我发现使用这个成语的几个代码示例。
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);
}
}
令我惊讶的是, :
-
Fire(Hello 1);
产生了两个消息, -
Fire(Hello 2);
产生了一条消息!
这使我相信取消注册new
代表作品! -
Fire(Hello 3);
NullReferenceException
。
调试代码显示Fire
是null
在注销事件之后。
Fire("Hello 1");
produced two messages, as expected.Fire("Hello 2");
produced one message!
This convinced me that unregisteringnew
delegates works!Fire("Hello 3");
threw aNullReferenceException
.
Debugging the code showed thatFire
isnull
after unregistering the event.
我知道对于事件处理程序和委托,编译器生成了很多代码在幕后但是我仍然不明白为什么我的推理是错误的。
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.
我缺少什么?
其他问题从 Fire
是 null
的事实,当没有注册任何事件时,我得出结论,到处都是触发事件,需要对 null
的支票。
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#编译器允许您在添加/删除事件处理程序时使用速记语法(在后台生成完全相同的代码):您可以省略 new 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");
它分配给一个临时的局部变量,以防止可能的竞争条件与其他线程上的取消订阅事件处理程序。 (有关分配事件的线程安全性的详细信息,请参阅我的博文)处理程序到一个局部变量。)另一种抵御此问题的方法是创建一个始终订阅的空代理;而这会使用更多的内存,事件处理程序永远不能为空(代码可以更简单):
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屋!