匿名方法作为事件处理程序不会泄漏吗?

5
每当您向事件处理程序添加委托时,您应该稍后将其删除,对吗?那么,如果您将匿名方法附加到事件上,由于您无法稍后删除它,这会创建事件处理程序泄漏吗?但是,此代码示例似乎暗示这是一种可行的做法
// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };

这真的是一个好的实践吗?

5
不必总是在后面移除事件处理程序。 - phoog
4个回答

11
无论何时添加委托到事件处理程序,您都应该稍后将其移除,对吗?
不一定。通常情况下,您希望事件处理程序保持有效,就像事件本身可以被引发的时间一样长-在UI中这是非常常见的。
这真的是一个好的做法吗?
绝对没问题,只要您不需要取消挂钩处理程序。考虑一下何时取消挂钩事件处理程序的时机。如果是“当表单(或按钮等)有资格进行垃圾回收”那么移除处理程序有什么好处呢?让它随着表单被垃圾回收就行了...

另一方面,如果一个按钮需要在 UI 处于一种状态时执行一项操作,在 UI 处于另一种状态时执行不同的操作,并且您必须附加/分离处理程序来完成此操作(例如“刷新”按钮,它将仅刷新选项卡控件的当前页面内容),那么这就是一个问题 - KeithS
@KeithS:是的 - 如果你处于必须解除处理程序的情况下,基本上你需要保留对它们的引用。这在其他问题中已经涉及到了 - 我只是在讨论事件处理程序是否总是需要取消订阅的想法。 - Jon Skeet

6
每当你添加一个委托到事件处理程序时,你应该稍后删除它,对吧?
其实,并不总是需要这么做。以下是你想要删除你所添加的事件处理程序的两个原因:
1. 你正在不断地向同一个实例添加短暂的处理程序。如果你不删除它们,那么会添加更多的处理程序,而大部分都是不需要的。
2. 你的处理程序在内部保留了对一个对象的引用,该对象的生存期比事件所属对象的生存期短得多,当其他对象超出范围时,事件处理程序将无法被调用。保留事件处理程序将强制使其在内存中停留的时间比预期的长,或者可能导致使用“陈旧”的对象,不再应该使用它们(例如,如果资源已经被处理,则不希望该事件再次触发)。
#2之所以成为问题,是因为C#中垃圾回收的工作方式。它标记所有可以确定处于作用域内的对象为“活动”,然后跟随任何这些“活动”对象引用的所有内容,直到跟随每个活动对象中的每个引用。从未标记为“活动”的任何内容都被视为“死亡”,并且有资格进行垃圾回收。
当你将事件处理程序附加到一个事件时,该委托包含两个内容:一个对象的实例和在该对象上运行的方法。只有当以下情况之一发生时,引用的对象才能被垃圾回收:
1. 事件所属的对象不再处于“活动”状态。
2. 你删除事件处理程序(即引用),允许更早地释放你的对象。
话虽如此,大部分情况都不适用于这两种情况,因此没有必要去删除事件处理程序。
例如,我经常看到人们在事件对象超出范围之前删除事件处理程序。这是无意义的。如果该对象超出了作用域,那么它持有对......任何内容的引用也没有问题。
现在,如果你正处于那些需要取消订阅事件处理程序的少数情况,并且你正在使用匿名方法,你需要……不要这样做。只需创建一个可以将其命名为方法的类并使用它。

在其中加入一些关于垃圾回收器的内容,就完美了。 - SimpleVar
就像我说的一样,完美。 :) - SimpleVar

3

使用匿名方法(或lambda表达式)订阅事件可能会导致内存泄漏,但在这种情况下不会。

在这种情况下,编译器将在当前类中生成一个匿名方法,只要您的按钮不比对象存在时间更长,就不会出现任何与内存相关的问题。


3
使用内联匿名委托作为事件处理程序确实会防止这些处理程序被移除,正如您所述。这可能会导致问题,特别是如果委托来自不同的对象。如果您有一个具有公共事件E的类A,并且该事件E由类B(与A没有包含/包含关系)订阅,将匿名处理程序H附加到E上,则H在技术上是B的成员,因此只要它被引用(通过附加到A的事件),B就不会被GCed。 B的生命周期与A的生命周期绑定在一起,如果您不希望或预期出现这种情况,则现在存在内存泄漏。

现在,这并非总是一个问题。如果B包含A,或者反之亦然,则A和B的生命周期已经彼此绑定;在您的情况下,按钮本身很可能在附加处理程序的容器控件之前被处理掉。同样,A和B可以是长寿命对象(例如,程序的主窗体和数据存储库),并且在程序终止之前不会被GCed。只要您从不必关心A和B之间的生命周期差异,并且您从不需要分离H以停止展示H的任何行为,那么您可以附加所有匿名处理程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接