如果我在这段代码片段中没有调用 pen
对象的 Dispose
方法,会发生什么?
private void panel_Paint(object sender, PaintEventArgs e)
{
var pen = Pen(Color.White, 1);
//Do some drawing
}
这里需要进行一些更正:
关于Phil Devaney的回答:
"...调用Dispose()方法可以进行可确定的清理,强烈推荐。"
实际上,在.NET中调用Dispose()方法并不会强制进行GC垃圾回收,即仅仅因为调用了Dispose()方法并不意味着立即触发GC回收操作。它只是间接地向GC发出信号,表示该对象可以在下一次GC(对于对象所处的代)进行清理。换句话说,如果该对象存在于第一代中,则它直到进行第一代垃圾回收才会被清理。虽然可以通过调用GC.Collect()方法来以编程方式和确定性地触发GC垃圾回收操作(尽管这不是唯一的方式),但不建议这样做,因为GC会根据运行时对应用程序内存分配的指标进行自我“调优”。调用GC.Collect()方法将导致这些指标被清除,并导致GC重新开始“调优”过程。
关于回答:
IDisposable用于释放非托管资源。这是.NET中的模式。
这个说法不完全正确。由于GC具有非确定性,因此Dispose模式(如何正确实现Dispose模式)可用于释放您正在使用的资源,无论是托管还是非托管。它与您要释放的资源类型没有任何关系。实现Finalizer是否必要则取决于您使用的资源类型-即仅在具有非可终结(即本地)资源时才实现Finalizer。也许您会混淆这两个概念。顺便提一下,您应该使用SafeHandle类来避免实现Finalizer,该类包装通过P/Invoke或COM互操作进行封送的本地资源。如果确实需要实现Finalizer,则始终应实现Dispose模式。
还有一点需要注意并且我还没有看到任何人提到,那就是如果创建了可清理对象并且它具有终结器(你永远不知道它们是否有 - 你肯定不应该对此做任何假设),那么它将直接发送到终结器队列,并至少存在一个额外的垃圾收集。
如果最终未调用GC.SuppressFinalize(),则对象的终结器将在下一次垃圾回收时调用。请注意,Dispose模式的适当实现应调用GC.SuppressFinalize()。因此,如果您在对象上调用Dispose(),并且它已正确实现该模式,则可以避免执行终结器。 如果您没有调用具有终结器的对象上的Dispose(),则对象将由垃圾回收器在下一次收集时执行其终结器。为什么这很糟糕?CLR中的终结器线程一直单线程,包括.NET 4.6以前。想象一下如果您增加了对此线程的负担会发生什么- 应用程序性能会急剧下降。
在对象上调用Dispose提供以下功能:
编辑: 我刚才注意到“无所不知且总是正确”的MSDN文档关于IDisposable(极其讽刺)实际上说
此接口的主要用途是释放非托管资源
众所周知,MSDN远非正确,从未提到或展示“最佳实践”,有时提供的示例没有编译。不幸的是,这些话记录在档案中。但是,我知道他们想说什么:在一个完美的世界里,GC将为您清理所有托管资源(多么理想化);但它不会清理非托管资源。这是绝对真实的。话虽如此,生活并不完美,任何应用程序也都不是。 GC只会清理没有根引用的资源。 这通常是问题所在。
在大约15-20种.NET可能“泄漏”(或不释放)内存的方式中,如果不调用Dispose()最可能影响你的是未注销/取消挂钩/解除连接事件处理程序/委托。如果创建了一个对象,并将委托连接到它上面,而您不调用Dispose()(也不自己分离委托),则GC仍将视该对象具有根引用 - 即委托。因此,GC永远不会对其进行收集。
@ joren的以下评论/问题(我的回复太长无法作为评论):
我写了一篇关于Dispose模式的博客文章,我建议使用它 - (如何正确实现 Dispose 模式)。有时候您应该将引用设置为 null,这样做永远不会有坏处。实际上,在垃圾回收运行之前,设置为 null 确实会做一些事情 - 它会删除对该对象的根引用。稍后,垃圾回收器会扫描其根引用的集合,并收集那些没有根引用的对象。当您应该这样做时,请考虑以下示例:您有一个类型为“ClassA”的实例 - 让我们称其为 'X'。 X 包含一个类型为“ClassB”的对象 - 让我们称其为 'Y'。 Y 实现了 IDisposable,因此,X 也应该这样做以处理 Y 的释放。假设 X 在第二代或 LOH 中,而 Y 在第 0 或 1 代中。当在 X 上调用 Dispose() 并且该实现将对 Y 的引用设置为 null 时,对 Y 的根引用立即被删除。如果 Generation 0 或 Generation 1 进行 GC,则会清除 Y 的内存/资源,但不会清除 X 的内存/资源,因为 X 存在于第二代或 LOH 中。
Dispose
方法,Pen
对象都将在未来某个不确定的时间被 GC(垃圾回收器)回收。Pen
持有的任何非托管资源(例如 GDI+ 句柄)将不会被 GC 清理。GC 仅清理托管资源。调用 Pen.Dispose
方法可确保及时清理这些非托管资源,以防资源泄漏。Pen
有一个终结器并且该终结器清理了非托管资源,则当 GC 回收 Pen
时,这些非托管资源将被清理。但重点是:
Dispose
以释放您的非托管资源,并且Pen
实现了 IDisposable
接口。 IDisposable
用于释放非托管资源。这是 .NET 中的惯用做法。在未来的某个不确定时间,即当 Pen 对象被垃圾回收并调用对象的终结器时,底层 GDI+ 笔柄将不会被释放。这可能直到进程终止才会发生,或者可能会更早,但关键是它是不确定性的。调用Dispose允许您进行确定性清理,并且强烈建议这样做。
它会保留资源,直到垃圾回收器清理它。
Component
的对象都实现了一个将调用 Dispose
的终结器。该终结器是非确定性的,这意味着您无法预测何时它会运行,但它最终总会运行。如果另一个终结器无限期地阻塞或进程被终止,则当然不会运行。 - ChaosPandion涉及图形处理时可能会出现很糟糕的情况。
打开Windows任务管理器。点击“选择列”,选择名为“GDI对象”的列。
如果您不处理某些图形对象,这个数字将不断增加。
在旧版本的Windows中,这可能会导致整个应用程序崩溃(据我所记,限制为10000),但对于Vista/7我不确定,但仍然是一件坏事。
GDI+
使用 GDI
对象吗?据我所知,WinForms 在大多数功能上使用的是 GDI+
而不是 GDI
。 - CodesInChaosDictionary<Color, Pen>
,它持续应用程序的时间,或者(4)拥有一个全局的Dictionary<Color, WeakReference>
,它将保存对每个笔的WeakReference
? - supercatWeakReference
方法也似乎有一定的优点,但是许多控件可能希望保存相同的对象(因此保存特定对象的控件数量可能从数百个到零不等)。 - supercat垃圾回收器最终会回收它,但是时间很重要: 如果您没有调用不再使用的对象的dispose方法,它将在内存中存活更长时间,并被提升到更高的代,这意味着回收它的成本更高。
在我脑海中首先浮现的想法是,这个对象将在方法执行完毕后立即被处理掉!我不知道我从哪里得到了这个信息!这是正确的吗?
pen
超出作用域后仍然存在。因此,您必须等待垃圾回收器决定何时收集可能晚得多的Pen
。 - CodesInChaosmyobj.Dispose()
,那么您必须使用using
语句 来包装您的代码。 - Cheng Chen