4

每当您将委托添加到事件处理程序时,您应该稍后将其删除,对吗?因此,如果您将匿名方法附加到事件,这是否会导致事件处理程序泄漏,因为您以后无法删除它?这个来自http://msdn.microsoft.com/en-us/library/0yw3tz5k%28VS.80%29.aspx的代码示例似乎暗示这是一个不错的做法。

// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };

这真的是一个好的做法吗?

4

4 回答 4

11

每当您将委托添加到事件处理程序时,您应该稍后将其删除,对吗?

不一定,不。通常,只要可以引发事件本身,您就希望事件处理程序保持有效——这在 UI 中当然很常见。

这真的是一个好的做法吗?

绝对可以,只要您不需要解开处理程序。想想你在什么时候解开事件处理程序。如果它是“当表单(或按钮或其他任何东西)可以进行垃圾收集时”,那么删除处理程序有什么好处?就让它用表格被垃圾收集......

于 2012-11-29T22:00:58.447 回答
6

每当您将委托添加到事件处理程序时,您应该稍后将其删除,对吗?

好吧,并非总是如此。您想要删除要添加的事件处理程序的原因有两个:

  1. 您不断地向同一个实例添加处理程序,但这些处理程序是短暂的。如果您没有删除它们,那么将添加越来越多的处理程序,而其中大多数都不需要。
  2. 您的处理程序在内部保留对一个对象的引用,该对象的生命周期比事件所属的任何对象的生命周期短得多,并且一旦其他对象超出范围,事件处理程序将不会(或不能)被调用. 保留事件处理程序将强制它在内存中停留的时间比预期的要长,或者可能导致使用“陈旧”且不应再使用的对象。(例如,如果资源已被释放,您不希望再触发该事件。)

#2 是一个问题的原因是垃圾收集在 C# 中的工作方式。它将所有可以 100% 确定在范围内的对象标记为“活动”,然后将所有这些“活动”对象引用的所有对象都标记为“活动”,直到它遵循每个活动对象中的每个引用。任何从未被标记为“活着”的东西都被认为是“死的”并且有资格进行垃圾收集。

当您将事件处理程序附加到委托包含两件事的事件时,一个对象的实例和一个在该对象上运行的方法。该引用的对象将无法被垃圾收集,直到:

  1. 带有事件的对象不再“活着”。
  2. 您删除了事件处理程序(即对您的委托的引用),从而允许您的对象更早地被释放。

That said, a significant percentage of cases don't apply to either of those, so there's no need to bother removing the event handlers.

As an example, I often see people removing event handlers just before the event object goes out of scope. That's pointless. If the object is out of scope there's no problem with it holding onto references to...whatever.

Now, if you are in one of those few situations in which you do need to unsubscribe the event handler, and you're using an anonymous method you need to...not. Just create a class that can make it a named method and use that.

于 2012-11-29T22:02:42.837 回答
3

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

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

于 2012-11-29T22:01:48.643 回答
3

正如您所说,使用内联匿名委托作为事件处理程序确实可以防止删除这些处理程序。这确实会导致问题,特别是如果委托来自不同的对象。如果您有一个带有公共事件 E 的 A 类,并且 E 被一个附加匿名处理程序 H 的 B 类(与 A 没有包含/包含关系)订阅,则 H 在技术上是 B 的成员,因此只要正如它所提到的(通过附加到 A 的事件),B 不会被 GCed。B 的生命周期与 A 的生命周期相关,如果您不希望或不希望现在发生内存泄漏。

现在,这并不总是一个问题。如果 B 包含 A,或者相反,那么 A 和 B 的生命周期无论如何都已经绑定在一起了;在您的情况下,按钮本身很可能会在附加处理程序的包含控件之前被处理掉。类似地,A 和 B 可能是长期存在的对象(例如,程序的主窗体和数据存储库)并且在程序终止之前不会被 GC。只要您永远不必关心 A 和 B 之间的生命周期差异,并且您永远不需要将 H 与 E 分离以停止展示 H 的任何行为,那么您可以附加所有您喜欢的匿名处理程序。

于 2012-11-29T22:19:56.417 回答