Proper use of the IDisposable interface
我从微软文档中了解到,
对我来说,"非托管"指的是数据库连接、套接字、窗口句柄等。但是,我看到过一些代码,其中实现
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } |
我的问题是,这是否使
编辑:到目前为止,人们已经发布了一些使用IDisposable清理非托管资源(如数据库连接和位图)的好例子。但是假设上面代码中的
Dispose的目的是释放非托管资源。这需要在某个时刻完成,否则它们将永远不会被清理干净。垃圾收集器不知道如何对
Note: What is an unmanaged resource? If you found it in the Microsoft .NET Framework: it's managed. If you went poking around MSDN yourself, it's unmanaged. Anything you've used P/Invoke calls to get outside of the nice comfy world of everything available to you in the .NET Framework is unmanaged – and you're now responsible for cleaning it up.
Ok.
您创建的对象需要公开一些外部世界可以调用的方法,以便清理非托管资源。该方法可以根据您的喜好命名:好的。
1 | public void Cleanup() |
或好的。
1 | public void Shutdown() |
但是,这种方法有一个标准化的名称:好的。
1 | public void Dispose() |
甚至还创建了一个接口,
1 2 3 4 | public interface IDisposable { void Dispose() } |
因此,您让对象公开
1 2 3 4 | public void Dispose() { Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); } |
你就完了。除非你能做得更好。好的。
如果对象已将250MB System.Drawing.Bitmap(即.NET托管位图类)分配为某种帧缓冲区,该怎么办?当然,这是一个托管.NET对象,垃圾收集器将释放它。但是,您真的想让250MB的内存留在那里——等待垃圾收集器最终出现并释放它吗?如果有一个开放的数据库连接怎么办?当然,我们不希望连接处于打开状态,等待GC完成对象。好的。
如果用户调用了
所以现在我们将:好的。
- 摆脱非托管资源(因为我们必须这样做),以及
- 摆脱被管理的资源(因为我们希望有帮助)
因此,让我们更新我们的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void Dispose() { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //Free managed resources too if (this.databaseConnection != null) { this.databaseConnection.Dispose(); this.databaseConnection = null; } if (this.frameBufferImage != null) { this.frameBufferImage.Dispose(); this.frameBufferImage = null; } } |
一切都很好,除了你能做得更好!好的。
如果那个人忘了打电话给你的物品上的
Note: They won't leak managed resources, because eventually the garbage collector is going to run, on a background thread, and free the memory associated with any unused objects. This will include your object, and any managed objects you use (e.g. the
Bitmap and theDbConnection ).Ok.
如果这个人忘记打电话给
Note: The garbage collector will eventually free all managed objects.
When it does, it calls theFinalize
method on the object. The GC doesn't know, or
care, about your Dispose method.
That was just a name we chose for
a method we call when we want to get
rid of unmanaged stuff.Ok.
垃圾收集器销毁我们的对象是释放这些烦人的非托管资源的最佳时机。我们通过重写
Note: In C#, you don't explicitly override the
Finalize() method.
You write a method that looks like a C++ destructor, and the
compiler takes that to be your implementation of theFinalize() method:Ok.
1 2 3 4 5 | ~MyObject() { //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to Dispose(); //<--Warning: subtle bug! Keep reading! } |
但代码中有一个错误。你看,垃圾收集器在后台线程上运行;你不知道两个对象的销毁顺序。完全有可能,在您的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public void Dispose() { //Free unmanaged resources Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle); //Free managed resources too if (this.databaseConnection != null) { this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it this.databaseConnection = null; } if (this.frameBufferImage != null) { this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it this.frameBufferImage = null; } } |
因此,您需要的是一种方法,让
这样做的标准模式是让
这个内部方法可以被赋予一些任意的名称,如"corepose"或"myInternalDispose",但传统上称它为
1 | protected void Dispose(Boolean disposing) |
但更有用的参数名称可能是:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects) { //Free unmanaged resources Win32.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 //anymore if (itIsSafeToAlsoFreeManagedObjects) { if (this.databaseConnection != null) { this.databaseConnection.Dispose(); this.databaseConnection = null; } if (this.frameBufferImage != null) { this.frameBufferImage.Dispose(); this.frameBufferImage = null; } } } |
您将
1 2 3 4 | public void Dispose() { Dispose(true); //I am calling you from Dispose, it's safe } |
最后定稿人:好的。
1 2 3 4 | ~MyObject() { Dispose(false); //I am *not* calling you from Dispose, it's *not* safe } |
Note: If your object descends from an object that implements
Dispose , then don't forget to call their base Dispose method when you override Dispose:Ok.
1 2 3 4 5 6 7 8 9 10 11 | public override void Dispose() { try { Dispose(true); //true: safe to free managed resources } finally { base.Dispose(); } } |
一切都很好,除了你能做得更好!好的。
如果用户在您的对象上调用
这不仅是浪费,而且如果您的对象对上次调用
您会注意到在我的代码中,我小心地删除了对我已处理的对象的引用,因此我不会在垃圾对象引用上调用
当用户调用
1 2 3 4 5 6 | protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize) { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy ... } |
解决这个问题的方法是告诉垃圾收集器,它不需要费心完成对象——它的资源已经被清理,不需要再做任何工作。通过在
1 2 3 4 5 | public void Dispose() { Dispose(true); //I am calling you from Dispose, it's safe GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later } |
现在,用户已经调用了
- 释放的非托管资源
- 释放的托管资源
在GC中运行终结器没有意义——所有事情都处理好了。好的。我不能使用Finalize清理非托管资源吗?
The Finalize method is used to perform cleanup operations on unmanaged resources held by the current object before the object is destroyed.
Ok.
但是,对于
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
Ok.
那是哪一个?哪一个是我清理非托管资源的地方?答案是:好的。
It's your choice! But choose
Dispose .Ok.
您当然可以将非托管清理放到终结器中:好的。
1 2 3 4 5 6 7 | ~MyObject() { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //A C# destructor automatically calls the destructor of its base class. } |
问题在于,您不知道垃圾收集器何时可以完成对象。您的未管理、不需要、未使用的本地资源将一直保留,直到垃圾收集器最终运行。然后它将调用终结器方法;清理非托管资源。Object.Finalize的文档指出:好的。
The exact time when the finalizer executes is undefined. To ensure deterministic release of resources for instances of your class, implement a Close method or provide a
IDisposable.Dispose implementation.Ok.
这是使用
回答你最初的问题:为什么不现在释放内存,而不是当GC决定释放内存时?我有一个面部识别软件,现在需要去掉530MB的内部图像,因为它们不再需要了。当我们不这样做时:机器会磨成一个交换停止。好的。奖金读数
对于任何喜欢这个答案风格的人(解释原因,以及如何变得明显),我建议你阅读Don Box's Essential Com:好的。
- 直接链接:皮尔逊出版社第一章样本
- 磁铁:84BF0B960936D677190A2BE355858E80EF7542C0
在35页中,他解释了使用二进制对象的问题,并在你眼前发明了COM。一旦你了解了COM的原因,剩下的300页就显而易见了,只需详细说明微软的实现。好的。
我认为每一个处理过对象或COM的程序员至少应该读第一章。这是有史以来最好的解释。好的。额外奖励阅读
当你所知道的一切都被埃里克·利珀特错了好的。
It is therefore very difficult indeed to write a correct finalizer,
and the best advice I can give you is to not try.Ok.
好啊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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."); } } } |
Dispose模式的目的是提供一种机制来清理托管和非托管资源,并且何时清理取决于如何调用Dispose方法。在您的示例中,Dispose的使用实际上并不执行与Dispose相关的任何操作,因为清除列表对要释放的集合没有影响。同样,将变量设置为空的调用对GC也没有影响。
您可以查看本文,了解有关如何实现Dispose模式的更多详细信息,但它基本上是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class SimpleCleanup : IDisposable { // some fields that require cleanup private 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),它实际上在两种不同的情况下运行:
- disposing==true:该方法已由用户代码直接或间接调用。可以释放托管和非托管资源。
- disposing==false:运行时已从终结器内部调用了该方法,不应引用其他对象。只能释放非托管资源。
让GC负责清理的问题在于,您无法真正控制GC何时运行收集周期(您可以调用gc.collect(),但实际上不应该这样做),因此资源可能会停留在需要的时间之外。记住,调用Dispose()实际上不会导致收集循环,也不会以任何方式导致GC收集/释放对象;它只是提供了更具确定性地清理所使用资源的方法,并告诉GC已经执行了此清理。
IDisposable和Dispose模式的关键不是立即释放内存。调用dispose甚至有机会立即释放内存的唯一时间是处理disposing==false方案和操作非托管资源。对于托管代码,在GC运行一个收集周期之前,实际上不会回收内存,而这个收集周期实际上是您无法控制的(除了调用gc.collect(),我已经提到过这不是一个好主意)。
由于.NET中的字符串不使用任何未管理的资源,也不实现IDisposable,因此您的方案实际上无效,无法强制它们"清理"。
调用Dispose之后,不应再调用对象的方法(尽管对象应允许进一步调用Dispose)。因此,这个问题的例子是愚蠢的。如果调用Dispose,则可以丢弃对象本身。因此,用户应该放弃对整个对象的所有引用(将其设置为空),它内部的所有相关对象将自动被清除。
至于关于托管/非托管的一般问题以及其他答案中的讨论,我认为该问题的任何答案都必须从非托管资源的定义开始。
归根结底,你可以调用一个函数来将系统置于一个状态,还有另一个函数可以让它从那个状态恢复过来。现在,在典型的例子中,第一个函数可能是返回文件句柄的函数,第二个函数可能是对
但是-这是关键-它们可以是任何匹配的函数对。一个建立了一个国家,另一个摧毁了它。如果状态已生成但尚未被删除,则存在该资源的一个实例。您必须安排在正确的时间进行拆卸-资源不是由clr管理的。唯一自动管理的资源类型是内存。有两种:GC和堆栈。值类型由堆栈管理(或通过在引用类型中挂接一个工具),引用类型由GC管理。
这些函数可能导致可以自由交错的状态更改,或者可能需要完全嵌套。状态更改可能是线程安全的,也可能不是。
看看正义问题中的例子。对日志文件缩进的更改必须完全嵌套,否则都会出错。而且它们不太可能是螺纹安全的。
可以搭上垃圾收集器来清理未管理的资源。但是,只有当状态更改函数是线程安全的,并且两个状态的生命周期可以以任何方式重叠。所以正义的资源例子不能有终结器!这对任何人都没有帮助。
对于这些类型的资源,您只需实现
然后,您必须使用
缺少的部分是必须手动编写Dispose,并使其调用字段和基类。C++/CLI程序员不必这么做。大多数情况下,编译器会为它们编写代码。
还有一种选择,我更喜欢嵌套完美且不是线程安全的状态(除了其他任何情况外,避免IDisposable使您免去了与无法拒绝向实现IDisposable的每个类添加终结器的人发生争论的问题)。
不是编写类,而是编写函数。函数接受一个委托,以回调到:
1 2 3 4 5 6 7 8 9 10 11 12 | public static void Indented(this Log log, Action action) { log.Indent(); try { action(); } finally { log.Outdent(); } } |
然后一个简单的例子是:
1 2 3 4 5 6 7 8 9 10 11 | 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用作代码块,因此,就像您自己的控制结构用于与
如果资源的生命周期可能重叠,那么这种技术就不那么有用了,因为这样你就可以构建资源A,然后构建资源B,然后杀死资源A,然后杀死资源B。如果你强迫用户这样完美地嵌套,你就不能这样做。但是您需要使用
我使用IDisposable的场景:清理非托管资源、取消订阅事件、关闭连接
我用于实现IDisposable(而不是threadsafe)的习惯用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class MyClass : IDisposable { // ... #region IDisposable Members and Helpers private 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 } |
如果
我使用
根据斯科特的评论进行编辑:
The only time the GC performance metrics are affected is when a call the [sic] GC.Collect() is made"
从概念上讲,GC维护对象引用图的视图,以及从线程的堆栈帧对它的所有引用。这个堆可能非常大,跨越许多内存页。作为一种优化,GC缓存其对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生变化时,GC从内核接收通知,因此它知道页面变脏了,需要重新扫描。如果集合是gen0的,那么页面中的其他内容也可能发生变化,但gen1和gen2中的变化不大。有趣的是,对于将gc移植到mac以使Silverlight插件在该平台上工作的团队来说,mac os x中没有这些钩子。
另一个反对不必要的资源处置的观点是:想象一个过程正在卸载的情况。还可以想象这个过程已经运行了一段时间。很可能该进程的许多内存页已交换到磁盘上。至少它们不再在一级或二级缓存中。在这种情况下,卸载的应用程序没有必要将所有这些数据和代码页交换回内存,以便在进程终止时"释放"操作系统将释放的资源。这适用于托管甚至某些非托管资源。只有保持非后台线程活动的资源才能被释放,否则进程将保持活动。
现在,在正常执行期间,必须正确清理临时资源(如@fezmonkey指出的数据库连接、套接字、窗口句柄),以避免非托管内存泄漏。这些是必须处理的事情。如果您创建了一个拥有线程的类(我的意思是它创建了线程,并因此负责确保它停止,至少按照我的编码风格),那么这个类很可能必须实现
NET框架使用
是的,该代码是完全冗余和不必要的,它不会使垃圾收集器做任何它不做的事情(一旦MyCollection的一个实例超出范围,即),尤其是
回答你的编辑:有点。如果我这样做:
1 2 3 4 5 6 7 | public void WasteMemory() { var instance = new MyCollection(); // this one has no Dispose() method instance.FillItWithAMillionStrings(); } // 1 million strings are in memory, but marked for reclamation by the GC |
在内存管理方面,它的功能与此相同:
1 2 3 4 5 6 7 8 | 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 |
如果您真的需要立即释放内存,请致电
如果要立即删除,请使用非托管内存。
见:
- 全局封送处理
- 弗里赫格罗巴元帅
- 驱逐舰结构
我不会重复那些关于使用或释放未管理资源的常规内容,这些都已经被涵盖了。但我想指出一个常见的误解。给出以下代码
1 2 3 4 5 6 7 8 9 | Public Class LargeStuff Implements IDisposable Private _Large as string() 'Some strange code that means _Large now contains several million long strings. Public Sub Dispose() Implements IDisposable.Dispose _Large=Nothing End Sub |
我认识到一次性实现并不遵循当前的指导方针,但希望你们都能理解这个想法。现在,当调用Dispose时,释放了多少内存?答:没有。调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC可以这样做。这并不是说上面的模式不是一个好主意,遵循上面的模式实际上仍然是一个好主意。一旦Dispose被运行,就不会停止GC重新声明大型使用的内存,即使largestuff的实例可能仍然在作用域中。大字符串也可能在0代中,但largestuff的实例可能是2代,因此,内存将很快被重新声明。但是,没有必要添加一个决赛选手来调用上面显示的Dispose方法。这只会延迟对记忆力的重新要求,让决赛选手能够参加比赛。
在您发布的示例中,它仍然没有"立即释放内存"。所有内存都是垃圾收集的,但它可能允许在早期版本中收集内存。你得做些测试才能确定。
框架设计指南是指导方针,而不是规则。它们告诉您接口的主要用途、何时使用、如何使用以及何时不使用。
我曾经读过一段代码,它是在使用IDisposable失败时的一个简单的rollback()。下面的minitx类将检查dispose()上的标志,如果没有发生
1 2 3 4 5 6 |
我还看到了计时/日志代码做同样的事情。在这种情况下,dispose()方法停止计时器并记录块已退出。
1 2 3 4 |
这里有几个具体的例子,它们不执行任何非托管资源清理,但成功地使用IDisposable创建了更干净的代码。
除了它作为一种控制系统资源生命周期的方法的主要用途之外(完全被Ian的令人敬畏的回答所覆盖,Kudos!),IDisposable/Using组合也可用于范围(关键)全局资源的状态更改:控制台、线程、进程、任何全局对象(如应用程序实例)。
我写了一篇关于这个模式的文章:http://pragmatteek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
它说明了如何以可重用和可读的方式保护一些常用的全局状态:控制台颜色、当前线程区域性、Excel应用程序对象属性…
如果有什么不同的话,我希望代码的效率比不使用代码时要低。
调用clear()方法是不必要的,如果Dispose不这样做,GC可能不会这样做…
在示例代码中,
如果其他对象引用了
当然,如果这是一种情况,我会称之为破坏性设计——我只是指出(在学术上,我想)根据片段中没有显示的
大多数讨论"非托管资源"的一个问题是,它们没有真正定义这个术语,但似乎意味着它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码进行了接口,但从这种角度考虑非托管资源并没有帮助。
相反,人们应该认识到所有管理资源的共同点:它们都需要一个对象要求一些外部的"事情"代表它做一些事情,损害一些其他的"事情",而另一个实体同意这样做,直到进一步通知。如果这个物体被遗弃,消失得无影无踪,那么外界的"物体"就再也不需要为了不再存在的物体而改变它的行为了;因此,"物体"的有用性将被永久性地削弱。
因此,非托管资源表示某个外部"物"代表某个对象改变其行为的协议,如果该对象被放弃并不再存在,这将毫无用处地损害该外部"物"的有用性。管理资源是作为此类协议受益人的对象,但如果被放弃,则已签署接收通知的对象,并且将使用此类通知在其被销毁之前将其事务整理好。
定义的第一个。对于我来说,非托管资源意味着某个类,它实现IDisposable接口或使用对dll的调用创建的某个类。GC不知道如何处理这些对象。如果类只有值类型,那么我不认为该类是具有非托管资源的类。对于我的代码,我遵循以下实践:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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准备回收那些原本不可能收集到的资源。
一个主要的例子是循环引用。
虽然使用避免循环引用的模式是最好的做法,但是如果您最终得到(例如)一个引用回其"parent"的"child"对象,如果您只是放弃引用并依赖gc-plus(如果已实现终结器),则可能会停止父对象的gc集合,而不会调用它。
唯一的方法是通过在子级上将父级引用设置为空来手动中断循环引用。
在父级和子级上实现IDisposable是实现这一点的最佳方法。当对父级调用Dispose时,对所有子级调用Dispose,在子Dispose方法中,将父级引用设置为空。
对于
下面的示例显示了一个带有一些代码和注释的IDisposable模式的好示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | 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-teld-you-about
对于实际问题,如果您使用IDisposable清理占用大量内存的托管对象,那么简短的回答将是否定的。原因是,一旦您处置了IDisposable,就应该让它超出范围。此时,任何引用的子对象也超出了范围,将被收集。
唯一真正的例外是,如果在托管对象中捆绑了大量的内存,并且您已经阻塞了等待某个操作完成的线程。如果在调用完成后不需要这些对象,那么将这些引用设置为空可能会使垃圾收集器更快地收集它们。但是,该场景表示需要重构的坏代码,而不是IDisposable的用例。