理解C#中的订阅与取消订阅机制

C#编程中,事件是一种常用的通知机制,允许对象通知其他对象发生了某些事情。然而,有时会遇到订阅事件时一切正常,但在尝试取消订阅时却遇到问题的情况。本文将探讨这一现象的原因,并提供相应的解决方案。

问题的根源

问题的核心在于C#编译器如何处理和转换匿名方法。在C#中,匿名方法可以用于创建事件处理器,但编译器在处理这些匿名方法时会创建一个封闭类来存储状态。这意味着每次调用匿名方法时,都会创建一个新的对象实例,这会导致取消订阅时出现问题。

以下是导致问题的示例代码:

public void Initialize() { control.KeyPressed += IfEnabledThenDo(control_KeyPressed); control.MouseMoved += IfEnabledThenDo(control_MouseMoved); } public void Destroy() { control.KeyPressed -= IfEnabledThenDo(control_KeyPressed); control.MouseMoved -= IfEnabledThenDo(control_MouseMoved); } public EventHandler IfEnabledThenDo(EventHandler actualAction) { return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); }; }

编译器将上述代码转换为:

public EventHandler IfEnabledThenDo(EventHandler actualAction) { <>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1(); CS$<>8__locals2.actualAction = actualAction; return new EventHandler<Control.ControlEventArgs>(CS$<>8__locals2.<IfEnabledThenDo>b__0); }

每次调用IfEnabledThenDo方法时,都会创建一个新的匿名类实例,因此取消订阅时无法正确移除事件处理器。

解决方案

要解决这个问题,需要缓存IfEnabledThenDo方法返回的委托实例,并在订阅和取消订阅时使用相同的实例。以下是修改后的代码:

EventHandler keyPressed; EventHandler mouseMoved; public void Initialize() { keyPressed = IfEnabledThenDo(control_KeyPressed); mouseMoved = IfEnabledThenDo(control_MouseMoved); control.KeyPressed += keyPressed; control.MouseMoved += mouseMoved; } public void Destroy() { control.KeyPressed -= keyPressed; control.MouseMoved -= mouseMoved; }

通过这种方式,确保了订阅和取消订阅使用的是相同的委托实例,从而避免了取消订阅时的问题。

深入理解

深入理解编译器如何处理匿名方法对于解决这类问题非常有帮助。Raymond Chen在他的博客中详细解释了编译器为何要这样处理匿名方法,主要是为了在事件处理器执行时能够“持有”actualAction参数。

通过本文的探讨,了解到了C#中订阅与取消订阅机制的工作原理,以及如何避免在取消订阅时遇到的问题。通过缓存委托实例,可以确保订阅和取消订阅时使用的是相同的对象实例,从而避免潜在的错误。

额外提示

实际上,对原始示例代码进行一个非常小的语法更改就可以立即解决问题。如果已经跟随本文到这里,应该能够理解为什么。

public void Initialize() { actualAction = control_KeyPressed; control.KeyPressed += IfEnabledThenDo(); } public void Destroy() { control.KeyPressed -= IfEnabledThenDo(); } EventHandler actualAction; public EventHandler IfEnabledThenDo() { return (sender, args) => { if (args.Control.Enabled) actualAction(sender, args); }; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485