正确使用IDisposable接口

我从阅读微软留档中知道IDisposable接口的“主要”用途是清理非托管资源。

对我来说,“非托管”意味着数据库连接、套接字、窗口句柄等,但是,我已经看到代码中实现了Dispose()方法来释放管理资源,这对我来说似乎是多余的,因为垃圾收集器应该为你处理这些。

例如:

public class MyCollection : IDisposable{private List<String> _theList = new List<String>();private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();
// Die, clear it up! (free unmanaged resources)public void Dispose(){_theList.clear();_theDict.clear();_theList = null;_theDict = null;}}

我的问题是,这是否会使MyCollection使用的垃圾收集器空闲内存比正常情况下更快?


编辑:到目前为止,人们已经发布了一些使用IDisposable清理非托管资源(例如数据库连接和位图)的好例子。但是假设上面代码中的_theList包含一百万个字符串,并且您想释放该内存现在,而不是等待垃圾回收器。上面的代码会实现吗?

399716 次浏览

是的,这段代码是完全冗余和不必要的,它不会让垃圾收集器做任何它不会做的事情(一旦MyCollection的实例超出范围,也就是说。)尤其是.Clear()调用。

回答您的编辑:有点。如果我这样做:

public void WasteMemory(){var instance = new MyCollection(); // this one has no Dispose() methodinstance.FillItWithAMillionStrings();}
// 1 million strings are in memory, but marked for reclamation by the GC

为了内存管理的目的,它在功能上与此相同:

public void WasteMemory(){var instance = new MyCollection(); // this one has your Dispose()instance.FillItWithAMillionStrings();instance.Dispose();}
// 1 million strings are in memory, but marked for reclamation by the GC

如果您真的真的需要立即释放内存,请调用GC.Collect()。不过,这里没有理由这样做。内存将在需要时被释放。

IDisposable通常用于利用using语句并利用一种简单的方法对托管对象进行确定性清理。

public class LoggingContext : IDisposable {public Finicky(string name) {Log.Write("Entering Log Context {0}", name);Log.Indent();}public void Dispose() {Log.Outdent();}
public static void Main() {Log.Write("Some initial stuff.");try {using(new LoggingContext()) {Log.Write("Some stuff inside the context.");throw new Exception();}} catch {Log.Write("Man, that was a heavy exception caught from inside a child logging context!");} finally {Log.Write("Some final stuff.");}}}

如果MyCollection无论如何都要被垃圾回收,那么您不应该处理它。这样做只会使CPU过度波动,甚至可能使垃圾回收器已经执行的一些预先计算的分析无效。

我使用IDisposable来做一些事情,比如确保线程被正确处理,以及非托管资源。

编辑回应斯科特的评论:

GC性能指标受到影响的唯一时间是调用[sic]GC. Collect()时。

从概念上讲,GC维护一个对象引用图的视图,以及所有对它的引用都来自线程的栈帧。这个堆可能非常大,并且跨越许多内存页。作为优化,GC缓存了对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生变化时,GC会收到内核的通知,因此它知道该页面是脏的,需要重新扫描。如果集合在Gen0中,那么页面中的其他内容很可能也在变化,但在Gen1和Gen2中这种可能性较小。有趣的是,这些钩子在Mac OS X中不适用于将GC移植到Mac以使Silverlight插件在该平台上工作的团队。

另一个反对不必要地处置资源的观点:想象一个进程正在卸载。再想象一下,该进程已经运行了一段时间。该进程的许多内存页很可能已经被交换到磁盘上。至少它们不再在L1或L2缓存中。在这种情况下,正在卸载的应用程序没有必要将所有这些数据和代码页交换回内存来“释放”进程终止时操作系统无论如何都要释放的资源。这适用于托管甚至某些非托管资源。只有让非后台线程保持活动的资源才必须被处置,否则进程将保持活动状态。

现在,在正常执行期间,必须正确清理一些短暂资源(正如@fezMonkey指出的数据库连接、套接字、窗口句柄)以避免非托管内存泄漏。这些是必须处理的事情。如果你创建了一个拥有线程的类(我的意思是它创建了它,因此负责确保它停止,至少按照我的编码风格),那么该类很可能必须实现IDisposable并在Dispose期间拆除线程。

. NET框架使用IDisposable接口作为一个信号,甚至是警告,告诉开发人员这个类必须将被释放。我想不出框架中实现IDisposable的任何类型(不包括显式接口实现),其中处置是可选的。

Dispose模式的目的是提供一种机制来清理托管和非托管资源,何时清理取决于调用Dispose_s方法的方式。在您的示例中,Dispose_s的使用实际上并没有做任何与dispose_s相关的事情,因为清除列表对正在处理的集合没有影响。同样,将变量设置为null的调用也不会对GC产生影响。

你可以看看这个文章,了解更多关于如何实现Dispose模式的细节,但它基本上看起来像这样:

public class SimpleCleanup : IDisposable{// some fields that require cleanupprivate SafeHandle handle;private bool disposed = false; // to detect redundant calls
public SimpleCleanup(){this.handle = /*...*/;}
protected virtual void Dispose(bool disposing){if (!disposed){if (disposing){// Dispose managed resources.if (handle != null){handle.Dispose();}}
// Dispose unmanaged managed resources.
disposed = true;}}
public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}}

这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:

  • 处理==true:该方法已被用户的代码直接或间接调用。可以处理托管和非托管资源。
  • 处理==false:该方法已由运行时从终结器内部调用,您不应引用其他对象。只能处理非托管资源。

简单地让GC负责清理的问题是,你无法真正控制GC何时运行收集周期(你可以调用GC. Collect(),但你真的不应该),所以资源可能会停留得比需要的更长。请记住,调用Dispose()实际上并不会导致收集周期,也不会以任何方式导致GC收集/释放对象;它只是提供了更确定地清理所使用资源的方法,并告诉GC已经执行了此清理。

IDisposable和disposable模式的全部重点并不是立即释放内存。对Disposable的调用真正有机会立即释放内存的唯一一次是在处理disposing==false场景和操作非托管资源时。对于托管代码,内存实际上不会被回收,直到GC运行一个收集周期,这是你真正无法控制的(除了调用GC. Collect(),我已经提到过这不是一个好主意)。

您的场景并不真正有效,因为. NET中的字符串不使用任何未命名的资源并且不实现IDisposable,因此没有办法强制它们“清理”。

示例代码中的Dispose()操作所做的一些事情,可能具有由于MyCollection对象的正常GC而不会发生的效果。

如果_theList_theDict引用的对象被其他对象引用,那么List<>Dictionary<>对象将不会被收集,而是突然没有内容。如果没有像示例中那样的Dispose()操作,这些集合仍将包含它们的内容。

当然,如果是这种情况,我会称之为一个破碎的设计——我只是指出(学究地,我想)Dispose()操作可能不是完全多余的,这取决于是否有List<>Dictionary<>的其他用途没有显示在片段中。

Dispose释放非托管资源的点。它需要在某个时候完成,否则它们永远不会被清理。垃圾收集器不知道如何在类型为IntPtr的变量上调用DeleteHandle(),它不知道是否是否需要调用DeleteHandle()

说明:什么是非托管资源?如果您在Microsoft. NET Framework中找到了它:它是托管的。如果您自己在MSDN上闲逛,它是非托管的。您使用P/Invoke调用在. NET Framework中可用的所有东西的舒适世界之外获得的任何东西都是非托管的-您现在负责清理它。

你创建的对象需要公开一些方法,外界可以调用,以清理非托管资源。该方法可以命名为任何你喜欢的名称:

public void Cleanup()

public void Shutdown()

但是这种方法有一个标准化的名称:

public void Dispose()

甚至创建了一个接口IDisposable,它只有一个方法:

public interface IDisposable{void Dispose()}

所以你让你的对象暴露IDisposable接口,这样你就保证你已经编写了那个单一的方法来清理你的非托管资源:

public void Dispose(){Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);}

你完成了除非你能做得更好。


如果你的对象分配了一个250MB的System. Drawing. Bitmap(即. NET托管位图类)作为某种帧缓冲区怎么办?当然,这是一个托管. NET对象,垃圾收集器会释放它。但是你真的想把250MB的内存留在那里——等待垃圾收集器最终出现并释放它吗?如果有打开数据库连接呢?我们当然不希望那个连接打开,等待GC最终确定对象。

如果用户调用了Dispose()(意味着他们不再打算使用该对象),为什么不摆脱那些浪费的位图和数据库连接呢?

所以现在我们将:

  • 摆脱非托管资源(因为我们必须这样做),并且
  • 摆脱托管资源(因为我们希望有所帮助)

因此,让我们更新我们的Dispose()方法以摆脱这些托管对象:

public void Dispose(){//Free unmanaged resourcesWin32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources tooif (this.databaseConnection != null){this.databaseConnection.Dispose();this.databaseConnection = null;}if (this.frameBufferImage != null){this.frameBufferImage.Dispose();this.frameBufferImage = null;}}

一切都很好,但你可以做得更好


如果忘记在你的对象上调用Dispose()怎么办?那么他们会泄漏一些非托管资源!

备注:它们不会泄漏管理资源,因为垃圾回收器最终会在后台线程上运行,并释放与任何未使用对象关联的内存。这将包括您的对象以及您使用的任何托管对象(例如BitmapDbConnection)。

如果这个人忘记调用Dispose(),我们可以仍然保存他们的培根!我们仍然有办法称之为进行他们:当垃圾收集器最终释放(即完成)我们的对象时。

备注:垃圾回收器最终将释放所有托管对象。当它这样做时,它会调用#0方法。GC不知道,或者关心,关于处置方法。那只是我们选的名字当我们想要获取时,我们调用的方法#36825;的东西

垃圾收集器销毁我们的对象是释放这些讨厌的非托管资源的第一次。我们通过覆盖Finalize()方法来做到这一点。

备注:在C#中,您不会显式覆盖Finalize()方法。你写了一个看起来像 aC++破坏者的方法,然后编译器将其作为Finalize()方法的实现:

~MyObject(){//we're being finalized (i.e. destroyed), call Dispose in case the user forgot toDispose(); //<--Warning: subtle bug! Keep reading!}

但是该代码中有一个bug。你看,垃圾收集器在后台线程上运行;你不知道两个对象被销毁的顺序。在你的Dispose()代码中,你试图摆脱的管理对象(因为你想提供帮助)完全有可能不再存在:

public void Dispose(){//Free unmanaged resourcesWin32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources tooif (this.databaseConnection != null){this.databaseConnection.Dispose(); //<-- crash, GC already destroyed itthis.databaseConnection = null;}if (this.frameBufferImage != null){this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed itthis.frameBufferImage = null;}}

因此,您需要的是Finalize()告诉Dispose()它应该不接触任何管理资源(因为它们不再是可能不在那里)的方法,同时仍然释放非托管资源。

这样做的标准模式是让Finalize()Dispose()都调用第三(!)方法;如果您从Dispose()(而不是Finalize())调用它,则传递一个布尔值,这意味着释放托管资源是安全的。

这个内部方法可以被赋予了一些任意的名字,比如“CoreDisposs”或“MyInternalDisposs”,但传统上称之为Dispose(Boolean)

protected void Dispose(Boolean disposing)

但更有帮助的参数名称可能是:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects){//Free unmanaged resourcesWin32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose//(If I'm being called from Finalize then the objects might not exist//anymoreif (itIsSafeToAlsoFreeManagedObjects){if (this.databaseConnection != null){this.databaseConnection.Dispose();this.databaseConnection = null;}if (this.frameBufferImage != null){this.frameBufferImage.Dispose();this.frameBufferImage = null;}}}

然后将IDisposable.Dispose()方法的实现更改为:

public void Dispose(){Dispose(true); //I am calling you from Dispose, it's safe}

和您的终结器:

~MyObject(){Dispose(false); //I am *not* calling you from Dispose, it's *not* safe}

说明:如果你的对象是从一个实现了Dispose的对象派生出来的,那么当你重写Dispose时,不要忘记调用他们的基地 Dispose方法:

public override void Dispose(){try{Dispose(true); //true: safe to free managed resources}finally{base.Dispose();}}

一切都很好,但你可以做得更好


如果用户对您的对象调用Dispose(),则所有内容都已清理。稍后,当垃圾收集器出现并调用Finaliz时,它将再次调用Dispose

这不仅是浪费,而且如果您的对象对您已经从最后调用到Dispose()处理掉的对象有垃圾引用,您将尝试再次处理它们!

您会注意到,在我的代码中,我小心翼翼地删除了对我已处理的对象的引用,因此我不会尝试在垃圾对象引用上调用Dispose

当用户调用Dispose()时:句柄光标文件位图图标服务句柄被销毁。稍后,当垃圾收集器运行时,它将再次尝试销毁相同的句柄。

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize){//Free unmanaged resourcesWin32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy...}

解决这个问题的方法是告诉垃圾收集器它不需要费心完成对象-它的资源已经被清理,不需要更多的工作。你可以通过在Dispose()方法中调用GC.SuppressFinalize()来做到这一点:

public void Dispose(){Dispose(true); //I am calling you from Dispose, it's safeGC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later}

现在用户已经调用了Dispose(),我们有:

  • 释放非托管资源
  • 释放托管资源

在运行终结器的GC中没有任何意义-一切都已经处理好了。

我不能使用Finalization来清理非托管资源吗?

留档#0

Finaliz方法用于在销毁对象之前对当前对象持有的非托管资源执行清理操作。

但是MSDN留档也说,对于#0

执行与释放、释放或重置非托管资源关联的应用程序定义的任务。

那么到底是哪个呢?哪个是我清理非托管资源的地方?答案是:

这是你的选择!但选择Dispose

您当然可以将非托管清理放在终结器中:

~MyObject(){//Free unmanaged resourcesWin32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.}

这样做的问题是你不知道垃圾回收器什么时候会结束你的对象。你的未托管、不需要、未使用的本机资源将一直存在,直到垃圾回收器最终运行。然后它会调用你的终结器方法;清理未托管的资源。反对结束的留档指出了这一点:

终结器执行的确切时间未定义。为确保类实例的资源确定性释放,请实现关闭方法或提供#0实现。

这是使用Dispose清理非托管资源的优点;当非托管资源被清理时,您会知道并控制。它们的销毁是“确定性”


回答你最初的问题:为什么不现在释放内存,而不是在GC决定这样做的时候?我有一个面部识别软件,它可以需要摆脱530 MB的内部图像现在,因为它们不再需要了。当我们不这样做时:机器就会停下来。

阅读奖励

对于任何喜欢这个答案风格的人(解释为什么,所以如何变得显而易见),我建议你阅读Don Box的Essential COM的第一章:

在35页中,他解释了使用二进制对象的问题,并在你眼前发明了COM。一旦你意识到COM的为什么,剩下的300页是显而易见的,只是详细说明了微软的实现。

我认为每一个曾经处理过对象或COM的程序员至少应该阅读第一章。这是有史以来最好的解释。

额外奖励阅读

当你知道的一切都是错的存档 Eric Lippert

因此编写一个正确的终结器确实非常困难,我能给你的最好建议就是不要尝试

使用IDisposable的场景:清理非托管资源、取消订阅事件、关闭连接

我用于实现IDisposable(不是线程安全的)的习惯用法:

class MyClass : IDisposable {// ...
#region IDisposable Members and Helpersprivate bool disposed = false;
public void Dispose() {Dispose(true);GC.SuppressFinalize(this);}
private void Dispose(bool disposing) {if (!this.disposed) {if (disposing) {// cleanup code goes here}disposed = true;}}
~MyClass() {Dispose(false);}#endregion}

一个对象的方法被调用之后就不应该再有调用了(尽管一个对象应该可以容忍对Dispose的进一步调用)。因此,问题中的例子是愚蠢的。如果调用了Dispose,那么对象本身可以被丢弃。因此,用户应该丢弃对整个对象的所有引用(将它们设置为null),它内部所有相关的对象都会自动被清理掉。

至于关于托管/非托管的一般问题以及其他答案中的讨论,我认为对这个问题的任何回答都必须从非托管资源的定义开始。

归根结底,你可以调用一个函数来使系统进入状态,你可以调用另一个函数来使它恢复状态。现在,在典型的例子中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle的调用。

但是——这是关键——它们可以是任何匹配的函数对。一个建立状态,另一个拆解状态。如果状态已经建立但尚未拆解,那么资源的一个实例存在。你必须安排在正确的时间拆解——资源不由CLR管理。唯一自动管理的资源类型是内存。有两种:GC和堆栈。值类型由堆栈管理(或搭上引用类型的车),引用类型由GC管理。

这些函数可能会导致可以自由交错的状态更改,或者可能需要完美嵌套。状态更改可能是线程安全的,也可能不是。

看看Justin问题中的示例。对Log文件缩进的更改必须完全嵌套,否则就会出错。它们也不太可能是线程安全的。

可以搭上垃圾回收器来清理你的非托管资源。但前提是状态更改函数是线程安全的,并且两个状态的生命周期可以以任何方式重叠。所以正义的资源示例不能有终结器!它不会帮助任何人。

对于这些类型的资源,你可以只实现IDisposable,而不需要终结器。终结器是绝对可选的-它必须是。这在许多书籍中被掩盖甚至没有提到。

然后,您必须使用using语句才能确保调用Dispose。这本质上就像搭上堆栈的便车(因为终结器是GC,using是堆栈)。

缺少的部分是您必须手动编写Dispose并使其调用您的字段和基类。C++ /CLI程序员不必这样做。在大多数情况下,编译器为他们编写。

还有一个替代方案,我更喜欢嵌套完美且不线程安全的状态(除此之外,避免IDisposable可以避免与无法抗拒向每个实现IDisposable的类添加终结器的人发生争论的问题)。

不是编写类,而是编写一个函数。该函数接受一个委托以回调:

public static void Indented(this Log log, Action action){log.Indent();try{action();}finally{log.Outdent();}}

然后一个简单的例子是:

Log.Write("Message at the top");Log.Indented(() =>{Log.Write("And this is indented");
Log.Indented(() =>{Log.Write("This is even more indented");});});Log.Write("Back at the outermost level again");

传入的lambda用作代码块,因此就像您创建自己的控制结构以服务于与using相同的目的,只是您不再有调用者滥用它的任何危险。他们不可能无法清理资源。

如果资源是那种可能有重叠生命周期的资源,这种技术就不那么有用了,因为你希望能够构建资源A,然后资源B,然后杀死资源A,然后杀死资源B。如果你强迫用户像这样完美嵌套,你就不能这样做。但是你需要使用IDisposable(但仍然没有终结器,除非你实现了线程安全,这不是免费的)。

如果有的话,我希望代码比省略它时效率更少

调用Clear()方法是不必要的,如果Dispose不这样做,GC可能不会这样做。

在您发布的示例中,它仍然没有“立即释放内存”。所有内存都是垃圾回收的,但它可能允许在更早的一代中收集内存。您必须运行一些测试才能确定。


框架设计指南是指导方针,而不是规则。它们告诉您界面的主要用途、何时使用它、如何使用它以及何时不使用它。

我曾经阅读过使用IDisposable进行失败时简单的RollBack()的代码。下面的MiniTx类将检查Dispos()上的一个标志,如果Commit调用从未发生,它将自己调用Rollback。它添加了一层间接使调用代码更容易理解和维护。结果看起来像:

using( MiniTx tx = new MiniTx() ){// code that might not work.
tx.Commit();}

我还看到计时/日志代码做同样的事情。在这种情况下,Dispos()方法停止了计时器并记录了块已退出。

using( LogTimer log = new LogTimer("MyCategory", "Some message") ){// code to time...}

因此,这里有几个具体的示例,它们不进行任何非托管资源清理,但成功地使用IDisposable创建了更干净的代码。

我不会重复关于使用或释放非托管资源的常见内容,这些内容都已经介绍过了。但我想指出一个常见的误解。
给定以下代码

Public Class LargeStuffImplements IDisposablePrivate _Large as string()
'Some strange code that means _Large now contains several million long strings.
Public Sub Dispose() Implements IDisposable.Dispose_Large=NothingEnd Sub

我意识到Disposable实现不遵循当前的指导方针,但希望你们都明白。
现在,当调用Dispose时,释放了多少内存?

答案:无。







调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC可以这样做。这并不是说上面的不是一个好主意,遵循上面的模式实际上仍然是一个好主意。一旦运行了Dispos,没有什么可以阻止GC回收_Large使用的内存,即使LargeStuff的实例可能仍然在范围内。_Large中的字符串也可能在第0代,但LargeStuff的实例可能在第2代,所以内存会更快被回收。
不过,添加一个终结器来调用上面显示的Dispose方法是没有意义的。这只会延迟内存的回收以允许终结器运行。

IDisposable适合取消订阅事件。

大多数关于“非托管资源”的讨论的一个问题是,它们并没有真正定义这个术语,但似乎暗示它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码交互,但以这种方式思考非托管资源并没有帮助。

相反,我们应该认识到所有管理资源的共同点:它们都需要一个对象要求某个外部“事物”代表它做某事,损害其他一些“事物”,而另一个实体同意这样做,直到另行通知。如果对象被遗弃并消失得无影无踪,没有什么会告诉外部“事物”它不再需要代表不再存在的对象改变其行为;因此,“事物”的有用性将永久降低。

因此,非托管资源代表某个外部“事物”代表某个对象改变其行为的协议,如果该对象被放弃并不复存在,这将无益地损害该外部“事物”的有用性。托管资源是这样一个对象,它是这种协议的受益人,但如果它被放弃,它已经签署了接收通知的协议,并且将使用这种通知在它被销毁之前将其事务整理好。

如果要马上删除,请使用非托管内存

见:

除了它的主要用途是控制系统资源一生(完全被伊恩的精彩答案所覆盖,荣誉!),IDisposable/使用组合也可以用于范围(关键)全局资源的状态变化控制台线程过程,任何全局对象应用实例

我写了一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

它说明了如何以可重复使用可读的方式保护一些常用的全局状态:控制台颜色、当前线培养Excel应用程序对象属性

首先是定义。对我来说,非托管资源意味着某个类,它实现了IDisposable接口或通过调用dll创建的东西。GC不知道如何处理这些对象。如果类只有例如值类型,那么我不认为这个类是具有非托管资源的类。对于我的代码,我遵循以下实践:

  1. 如果我创建的类使用了一些非托管资源,那么这意味着我还应该实现IDisposable接口以清理内存。
  2. 一旦我用完它就清理对象。
  3. 在我的dispose方法中,我遍历类的所有IDisposable成员并调用Dispose。
  4. 为了通知垃圾收集器我的对象已经被清理,我这样做是因为调用GC是昂贵的操作。
  5. 作为额外的预防措施,我尝试多次调用Dispose()。
  6. 有时我添加私有成员_disposed并签入方法调用对象是否被清理。如果它被清理了,那么生成ObjectDisposedException
    以下模板演示了我用单词描述的代码示例:

public class SomeClass : IDisposable{/// <summary>/// As usually I don't care was object disposed or not/// </summary>public void SomeMethod(){if (_disposed)throw new ObjectDisposedException("SomeClass instance been disposed");}
public void Dispose(){Dispose(true);}
private bool _disposed;
protected virtual void Dispose(bool disposing){if (_disposed)return;if (disposing)//we are in the first call{}_disposed = true;}}

处置托管资源最合理的用例是为GC回收否则永远不会收集的资源做准备。

一个主要的例子是循环引用。

虽然使用避免循环引用的模式是最佳实践,但如果您最终得到(例如)一个具有返回其“父”引用的“子”对象,如果您只是放弃引用并依赖GC,这可能会停止父对象的GC收集-如果您已经实现了终结器,它永远不会被调用。

解决这一问题的唯一方法是通过将子级的父引用设置为null来手动打破循环引用。

在父级和子级上实现IDisposable是做到这一点的最佳方法。当在父级上调用Dispose时,在所有子级上调用Dispose,并在子级Dispose方法中,将父级引用设置为null。

您给定的代码示例不是IDisposable用法的好例子。字典清除通常不应该转到Dispose方法。字典项将在超出范围时被清除和处理。需要IDisposable实现来释放一些即使超出范围也不会释放/释放的内存/处理程序。

下面的示例显示了IDisposable模式的一个很好的示例,其中包含一些代码和注释。

public class DisposeExample{// A base class that implements IDisposable.// By implementing IDisposable, you are announcing that// instances of this type allocate scarce resources.public class MyResource: IDisposable{// Pointer to an external unmanaged resource.private IntPtr handle;// Other managed resource this class uses.private Component component = new Component();// Track whether Dispose has been called.private bool disposed = false;
// The class constructor.public MyResource(IntPtr handle){this.handle = handle;}
// Implement IDisposable.// Do not make this method virtual.// A derived class should not be able to override this method.public void Dispose(){Dispose(true);// This object will be cleaned up by the Dispose method.// Therefore, you should call GC.SupressFinalize to// take this object off the finalization queue// and prevent finalization code for this object// from executing a second time.GC.SuppressFinalize(this);}
// Dispose(bool disposing) executes in two distinct scenarios.// If disposing equals true, the method has been called directly// or indirectly by a user's code. Managed and unmanaged resources// can be disposed.// If disposing equals false, the method has been called by the// runtime from inside the finalizer and you should not reference// other objects. Only unmanaged resources can be disposed.protected virtual void Dispose(bool disposing){// Check to see if Dispose has already been called.if(!this.disposed){// If disposing equals true, dispose all managed// and unmanaged resources.if(disposing){// Dispose managed resources.component.Dispose();}
// Call the appropriate methods to clean up// unmanaged resources here.// If disposing is false,// only the following code is executed.CloseHandle(handle);handle = IntPtr.Zero;
// Note disposing has been done.disposed = true;
}}
// Use interop to call the method necessary// to clean up the unmanaged resource.[System.Runtime.InteropServices.DllImport("Kernel32")]private extern static Boolean CloseHandle(IntPtr handle);
// Use C# destructor syntax for finalization code.// This destructor will run only if the Dispose method// does not get called.// It gives your base class the opportunity to finalize.// Do not provide destructors in types derived from this class.~MyResource(){// Do not re-create Dispose clean-up code here.// Calling Dispose(false) is optimal in terms of// readability and maintainability.Dispose(false);}}public static void Main(){// Insert code here to create// and use the MyResource object.}}

我看到很多答案已经转移到讨论如何将IDisposable用于托管和非托管资源。我建议将本文作为我找到的关于如何实际使用IDisposable的最佳解释之一。

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

对于实际的问题;如果您使用IDisposable清理占用大量内存的托管对象,简短的答案将是。原因是一旦您的持有内存的对象超出范围,它就可以进行收集。此时,任何引用的子对象也超出范围并将被收集。

唯一真正的例外是,如果你有大量内存捆绑在托管对象中,并且你已经阻止了该线程等待某些操作完成。如果调用完成后不需要这些对象,那么将这些引用设置为null可能允许垃圾回收器更快地收集它们。但这种情况将代表需要重构的糟糕代码-而不是IDisposable的用例。