如果我不在笔对象上调用Dispose会发生什么?

And*_*nck 37 c# dispose winforms

如果我不调用此代码段中Disposepen对象会发生什么?

private void panel_Paint(object sender, PaintEventArgs e)
{
    var pen = Pen(Color.White, 1);
    //Do some drawing
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*ack 28

这里应该做一些修正:

关于Phil Devaney的答案:

"......调用Dispose允许您进行确定性清理,强烈推荐."

实际上,调用Dispose()并不能确定性地导致.NET中的GC集合 - 即它不会因为您调用Dispose()而立即触发GC.它只向GC间接发出信号,表示在下一次GC期间可以清除对象(对象所在的生成).换句话说,如果对象位于第1代,则在第1代收集发生之前不会将其处理掉.可以通过编程和确定性地使GC执行集合的唯一方法(尽管不是唯一的方法)是调用GC.Collect().但是,建议不要这样做,因为GC会在运行时通过收集有关应用程序运行时内存分配的指标来"调整"自身.调用GC.Collect()会转储这些指标并导致GC重新开始"调整".

关于答案:

IDisposable用于处理非托管资源.这是.NET中的模式.

这是不完整的.由于GC是非确定性的,因此可以使用Dispose Pattern(如何正确实现Dispose模式),以便您可以释放您正在使用的资源 - 托管或非托管.它与您发布的资源无关.实现Finalizer的需要与您正在使用的资源类型有关 - 即,如果您具有非可终结(即本机)资源,则仅实现一个资源.也许你会混淆两者.顺便说一句,你应该避免使用SafeHandle类来实现Finalizer,而不是包装通过P/Invoke或COM Interop封送的本机资源.如果最终实现了Finalizer,则应始终实现Dispose Pattern.

我还没有看到任何人提到的一个重要注意事项是,如果创建了一次性对象并且它有一个Finalizer(并且你从未真正知道它们是否存在 - 并且你当然不应该对此做出任何假设),那么它将会直接发送到Finalization Queue并进行至少1次额外的GC收集.

如果最终未调用GC.SuppressFinalize(),则将在下一个GC上调用该对象的终结器.请注意,Dispose模式的正确实现应该调用GC.SuppressFinalize().因此,如果在对象上调用Dispose()并且它已正确实现了模式,则将避免执行Finalizer.如果不对具有终结器的对象调用Dispose(),则该对象将在下一个集合上由GC执行其Finalizer.为什么这么糟糕?CLR中的Finalizer线程(包括.NET 4.6)是单线程的.想象一下如果你增加这个线程的负担会发生什么 - 你的应用程序性能会告诉你在哪里.

在对象上调用Dispose可提供以下内容:

  1. 减少GC对该过程的压力;
  2. 减少应用程序的内存压力;
  3. 如果LOH(大对象堆)被分割并且对象在LOH上,则减少OutOfMemoryException(OOM)的可能性;
  4. 如果对象具有Finalizer,则将对象保留在Finalizable和f-reachable Queues之外;
  5. 确保以确定的方式清理您的资源(托管和非托管).

编辑:我刚刚注意到IDisposable (这里极端讽刺)的"所有知道并且始终正确"的MSDN文档确实说

此接口的主要用途是释放非托管资源

任何人都应该知道,MSDN远非正确,从未提及或显示"最佳实践",有时提供不编译的示例等.不幸的是,这些都记录在这些文字中.但是,我知道他们想说的是:在一个完美的世界里,GC会为你清理所有管理的资源(多么理想化); 然而,它不会清理非托管资源.这绝对是真的.话虽这么说,生活并不完美,也没有任何应用. GC只会清理没有rooted-references的资源. 这主要是问题所在.

在.NET可以"泄漏"(或不释放)内存的大约15-20种不同方式中,如果不调用Dispose(),最有可能咬你的方法是取消注册/取消挂起/取消/拆除事件处理器/代表.如果您创建一个具有与其连接的委托的对象,并且您没有在其上调用Dispose()(并且不自行分离委托),则GC仍会将该对象视为具有root权限的引用 - 即委托.因此,GC永远不会收集它.

@joren的评论/问题如下(我的回复太长,无法发表评论):

我有一篇关于我建议使用的Dispose模式的博客文章 - (如何正确实现Dispose模式).有些时候你应该删除引用,它永远不会伤害这样做.实际上,这样做会在GC运行之前做一些事情 - 它会删除对该对象的root权限引用.GC稍后会扫描其根植入的集合,并收集那些没有根参考的参考.想想这个例子,当这样做是好的时候:你有一个类型为"ClassA"的实例 - 我们称之为'X'.X包含一个"ClassB"类型的对象 - 让我们称之为'Y'.Y实现了IDisposable,因此,X应该做同样的事情来处理Y.假设X在第2代或LOH中,Y在第0代或第1代.当在X上调用Dispose()并且该实现为空时引用Y,立即删除对Y的有根引用.如果Gen 0或Gen 1发生GC,则清除Y的内存/资源,但X的内存/资源不是因为X存在于Gen 2或LOH中.

  • @davidcarr - 我认为你完全错过了我的回答的要点。避免在托管代码中调用 Dispose 是不行的。我已经详细解释了为什么 tit 是必要的。底线是:如果一个类实现了 IDisposable,那么您应该调用它。在一种情况下,由于某个类的实现方式而无需调用 Dispose 是一种糟糕的设计 - 您依赖于内部实现细节,这些细节应被视为黑匣子 - 例如 MemoryStream 和任何其他 .NET框架(或其他外部框架)类。内部实施发生变化。 (3认同)
  • @davidcarr - IDisposable 模式被如此误解的原因之一是人们(比如你自己)想要关心底层资源是否使用托管资源或非托管资源 - 好像这是决定因素 - 它不应该是决定因素。如果你坚持“如果它实现了 IDisposable,那么我应该调用 Dispose()”的想法,那么 100% 的情况下你都会没问题。如果人们遵循这个建议,那么该模式就不会产生任何混乱。 (3认同)
  • 对于一个明显的矛盾,Jason 表示“IDisposable 用于处置非托管资源”。其唯一目的是确定性地清理资源——无论它们是否受到管理。正确实现 Dispose 模式可以帮助您清理资源。我并不是说非托管资源没有被清理,而是说 IDisposable 的目的与您正在处置的资源类型正交。人们可以从他的声明中推测,除非您使用本机资源,否则您不需要实现 IDisposable - 这绝对是不正确的。 (2认同)
  • 我认为可以安全地假设,如果实现了 IDisposable,那么它具有需要以确定性方式释放的资源。是否存在非托管资源是一个实现细节。 (2认同)
  • @davidcarr 郑重声明,当我说“像你这样的人”时,我并没有任何不尊重的意思。我只是描述性的。 (2认同)

jas*_*son 25

Pen会由GC在未来某个不确定的点进行收集,你是否不叫Dispose.

但是,笔不会清除笔所持有的任何非托管资源(例如,GDI +句柄).GC仅清理托管资源.通过呼叫Pen.Dispose,您可以确保及时清理这些非托管资源,并确保不泄漏资源.

现在,如果Pen有一个终结器并且终结器清理了非托管资源,则当Pen收集垃圾时,将清除那些所述非托管资源.但重点是:

  1. 您应该Dispose明确调用,以便释放非托管资源,并且
  2. 如果有终结器并且它清理了非托管资源,您不必担心实现细节.

Pen实施IDisposable.IDisposable用于处置非托管资源.这是.NET中的模式.

有关此主题的先前评论,请参阅此答案.

  • -1表示GC不会清理非托管资源的断言.如果IDisposable实现正确执行此操作,它将在终结线程中正常工作,但只是稍后. (3认同)
  • 这个答案是不完整的,有点误导.我在下面的帖子中描述了原因. (2认同)
  • GC不会释放非托管内存或资源 - 无论Dispose()是如何实现的.它可以*做的是允许你调用Marshal.ReleaseComObject()并实现一个Finalizer,如果没有调用GC.SuppressFinalize(),它将被调用.仅仅因为在完成期间或在Dispose实现中清理非托管内存,并不意味着GC正在执行发布.GC不负责清理非托管资源 - 如果程序员从未这样做,那么内存将被泄露. (2认同)

Phi*_*ney 13

在将来某些不确定的时间,即Pen对象被垃圾收集并调用对象的终结器时,底层GDI +笔柄将不会被释放.这可能不会在流程终止之前,或者可能更早,但重点是它是非确定性的.调用Dispose允许您进行确定性清理,强烈建议使用.