7

据我了解,C# 中内存泄漏的主要原因之一是在处置其容器时未能取消注册事件侦听器。出于这个原因,每当我手动注册一个事件 - 例如 a Timer.Elapsed += ...- 我确保Timer.Elapsed -= ...当我完成对象(或父对象)时。

然而,我只是在查看一个 Windows 窗体设计器生成的类,并注意到虽然它很高兴地订阅了事件(例如this.button1.Click += new System.EventHandler(this.button1_Click);),但似乎除了默认操作之外没有任何清理过程components.Dispose();

这是否意味着Dispose()每个组件的方法都应该取消注册/取消订阅任何已绑定到它的事件?如果是这样,组件如何从它不知道的“外部”事件处理程序中注销,这是否意味着手动尝试从标准 [IDisposable] Windows 控件(计时器、按钮、表单等)中删除事件侦听器通常是不必要?

谢谢

4

3 回答 3

7

如果包含事件的对象的寿命比包含处理程序的对象长,事件处理程序只会导致内存泄漏。

在典型的 WinForms 场景中,控件和表单代码都只在表单打开时才存在,因此首先没有问题。

您只需从静态事件、单例或其他长期存在的对象中注销您的处理程序。

于 2013-10-29T21:10:17.813 回答
3

好的设计,主要是。对象模型经过精心设计,以确保事件源的寿命不会超过订阅者。当然有一个循环引用,表单通过它的 Controls 集合以及一个可能的私有变量来保持对控件的引用,控件通过事件订阅添加对表单的引用。但是控件的生命周期是由表单控制的,当用户关闭窗口时,两者都会失效。这删除了对表单对象的通常唯一引用,该对象保存在将句柄映射到表单的内部表中。GC 对循环引用没有任何问题。

有一些尖锐的边缘,Application.Idle 和 SystemEvents 事件很麻烦。他们在 MSDN Library 中有很多黄色磁带。

Disposal 也是自动的,不用于取消订阅 Winforms 中的事件,每个控件都会在其自己的 Controls 集合中处理引用。这从 Form 类开始并自动遍历树。覆盖表单的 Dispose() 方法是不寻常的,也往往会引起很多焦虑,因为该方法存在于表单的 Designer.cs 文件中。移动该方法很好,就像使用 FormClosed 事件作为替代方法一样处置。

不过,这与电锯的字节有一个锋利的边缘。在 Winforms 中处理控件不是可选的。非常不寻常,它在框架中的其他任何地方都是可选的,终结器备份忘记调用它。不在 Winforms 中,如果您使用 Controls.Clear 或 Remove,则不会释放您删除的控件。它被重新托管到一个称为“停车窗口”的隐藏窗口。保持控件活动以将其移动到另一个父级。不错的功能,除非您不将其移至其他地方。它将永远存在于那个隐藏的窗口上,非常讨厌的泄漏。不是很好的设计。

有一些模式可以解决事件的生命周期问题。“弱事件模式”在当今的 .NET 编程中是相当样板的。这是一个设计问题的信号标志,通常是由于喜欢观察者模式而引起的,因为它在 .NET 中运行良好,但不喜欢它附带的合同。霸道的对象模型几乎总是问题的根源,就像在 [winforms] 标签中不应提及其名称的三个字母首字母缩略词 :)

于 2013-10-29T22:09:59.627 回答
2

事件源将保留订阅者,而不是相反。当表单消失时,它将有资格获得 GC,这反过来会使听众有资格。

于 2013-10-29T21:11:00.330 回答