正确使用IDisposable接口
通过阅读MSDN文档 ,我知道IDisposable
接口的“主要”使用是清理非托pipe资源。
对我来说,“非托pipe”意味着像数据库连接,套接字,窗口句柄等东西。但是,我已经看到代码实施Dispose()
方法释放托pipe资源,这似乎对我来说是多余的,因为垃圾收集器应该照顾你的。
例如:
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
使用的垃圾回收器可用内存比MyCollection
更快?
编辑 :到目前为止,人们已经发布了一些使用IDisposable清理非托pipe资源(如数据库连接和位图)的好例子。 但是,假设上面的代码中的_theList
包含了一百万个string,并且您现在想要释放该内存,而不是等待垃圾收集器。 上面的代码会完成这个吗?
Dispose的要点是释放非托pipe资源。 它需要在某个时候完成,否则他们将永远不会被清理。 垃圾收集器不知道如何调用types为IntPtr
的variables的DeleteHandle()
,它不知道是否需要调用DeleteHandle()
。
注意 :什么是非托pipe资源 ? 如果您在Microsoft .NET Framework中find它,它将被pipe理。 如果你自己去了MSDN,那么它是不受pipe理的。 任何你使用过P / Invoke调用的东西都不在.NET框架中可用的所有东西的舒适之处,而且你现在负责清理它。
您创build的对象需要公开一些外部世界可以调用的方法,以便清理非托pipe资源。 该方法可以任意命名:
public void Cleanup() public void Shutdown()
但是,这个方法有一个标准化的名字:
public void Dispose()
甚至有一个创build的接口, IDisposable
,只有一个方法:
public interface IDisposable { void Dispose() }
所以你让你的对象公开IDisposable
接口,这样你就保证你已经编写了这个单一的方法来清理你的非托pipe资源:
public void Dispose() { Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); }
你完成了。 除非你能做得更好。
如果你的对象已经分配了一个250MB的System.Drawing.Bitmap (即.NETpipe理的Bitmap类)作为某种帧缓冲区? 当然,这是一个托pipe的.NET对象,垃圾收集器将释放它。 但是,你真的想离开250MB的内存吗?等待垃圾收集器最终来到并释放它? 如果有一个开放的数据库连接呢? 当然,我们不希望这个连接处于打开状态,等待GC完成对象。
如果用户调用Dispose()
(意味着他们不再计划使用该对象),为什么不摆脱这些浪费的位图和数据库连接呢?
所以现在我们会:
- 摆脱非托pipe资源(因为我们必须),和
- 摆脱pipe理资源(因为我们想要有帮助)
所以让我们更新我们的Dispose()
方法来摆脱这些pipe理对象:
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; } }
一切都很好, 除非你能做得更好 !
如果这个人忘了在你的对象上调用Dispose()
呢? 然后他们会泄露一些非托pipe资源!
注意:它们不会泄露托pipe资源,因为垃圾收集器最终将在后台线程上运行,并释放与任何未使用的对象关联的内存。 这将包括您的对象,以及您使用的任何pipe理对象(例如
Bitmap
和DbConnection
)。
如果这个人忘了叫Dispose()
,我们仍然可以保存他们的培根! 我们仍然可以为他们调用它:当垃圾回收器最终得到解放(即最终)我们的对象。
注意:垃圾收集器最终将释放所有的pipe理对象。 当它的时候,它调用对象上的
Finalize
方法。 GC不知道或关心Dispose方法。 这只是我们select的一个名字,当我们想要摆脱非pipe理性的东西时,我们称之为方法。
垃圾收集器破坏我们的对象是释放这些烦人的非托pipe资源的最佳时机。 我们通过重写Finalize()
方法来做到这一点。
注意:在C#中,您不显式重写
Finalize()
方法。 编写一个看起来像 C ++析构函数的方法,编译器将其作为Finalize()
方法的实现:
~MyObject() { //we're being finalized (ie destroyed), call Dispose in case the user forgot to Dispose(); //<--Warning: subtle bug! Keep reading! }
但是这个代码有一个bug。 你看,垃圾收集器运行在后台线程上 ; 你不知道两个对象被销毁的顺序。 在你的Dispose()
代码中,你试图摆脱的pipe理对象(因为你想要有帮助)完全有可能不再存在:
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; } }
所以你需要的是Finalize()
告诉Dispose()
它不应该触及任何托pipe资源(因为它们可能不在那里 ),同时还释放非托pipe资源。
这样做的标准模式是让Finalize()
和Dispose()
都调用第三个 (!)方法; 如果您从Dispose()
(而不是Finalize()
)调用它,则传递布尔值,这意味着释放托pipe资源是安全的。
这个内部的方法可以被赋予一些任意的名字,比如“CoreDispose”或者“MyInternalDispose”,但是传统的称之为Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
但更有用的参数名称可能是:
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; } } }
并且您将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 Dispose() { try { Dispose(true); //true: safe to free managed resources } finally { base.Dispose(); } }
一切都很好, 除非你能做得更好 !
如果用户在你的对象上调用了Dispose()
,那么所有的东西都被清除了。 稍后,当垃圾收集器出现并调用Finalize时,它将再次调用Dispose
。
这不仅是浪费,但是如果你的对象有垃圾引用,你已经从上次调用Dispose()
已经处理了的对象,你将尝试再次处理它们!
你会注意到在我的代码中,我仔细地删除了我已经Dispose
的对象的引用,所以我不试图在垃圾对象引用上调用Dispose
。 但是这并没有阻止一个微妙的错误蔓延。
当用户调用Dispose()
,句柄CursorFileBitmapIconServiceHandle被销毁。 稍后当垃圾收集器运行时,它将尝试再次销毁同一个句柄。
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize) { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy ... }
你解决这个问题的方法是告诉垃圾收集器它不需要打扰最终化对象 – 它的资源已经被清理了,不需要更多的工作。 您可以通过在Dispose()
方法中调用GC.SuppressFinalize()
来完成此操作:
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 }
现在,用户已经调用Dispose()
,我们有:
- 释放非托pipe资源
- 释放pipe理资源
GC运行终结器没有意义 – 一切都照顾好了。
我不能使用Finalize清理非托pipe资源吗?
Object.Finalize
的文档说:
Finalize方法用于在对象被销毁之前对当前对象持有的非托pipe资源执行清理操作。
但MSDN文档也说, IDisposable.Dispose
:
执行与释放,释放或重置非托pipe资源相关的应用程序定义的任务。
那是哪个呢? 哪一个是我清理非托pipe资源的地方? 答案是:
这是你的select! 但是select
Dispose
。
您当然可以将您的非托pipe清理放入终结器中:
~MyObject() { //Free unmanaged resources Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //AC# destructor automatically calls the destructor of its base class. }
这个问题是你不知道什么时候垃圾收集器会绕过来最终确定你的对象。 您的未pipe理,不需要,未使用的本地资源将坚持到垃圾收集器最终运行。 然后它会调用你的终结器方法; 清理非托pipe资源。 Object.Finalize的文档指出:
终结器执行的确切时间是未定义的。 为确保为您的类的实例确定性地释放资源,请实现Close方法或提供一个
IDisposable.Dispose
实现。
这是使用Dispose
清理非托pipe资源的优点; 你知道,并控制,当非托pipe资源被清理。 他们的破坏是“确定性的” 。
回答你原来的问题:为什么不现在释放记忆,而不是当GC决定去做? 我有一个面部识别软件,现在需要摆脱530 MB的内部图像,因为他们不再需要。 当我们不这样做的时候:机器正在交换中止。
奖金阅读
对于那些喜欢这个答案的风格的人来说(解释为什么 ,所以如何变得明显),我build议你阅读Don Box的基本COM的第一章:
- 直接链接: Pearson Publishing出版的第1章样本
- 磁铁:84bf0b960936d677190a2be355858e80ef7542c0
在35页中,他解释了使用二进制对象的问题,并在您的眼前发明COM。 一旦你意识到COM的原因 ,其余的300页是显而易见的,只是详细的微软的实施。
我认为每个曾经处理过对象或COM的程序员都应该至less读第一章。 这是有史以来最好的解释。
通常使用IDisposable
来利用using
语句,并利用一个简单的方法来确定性地清理pipe理对象。
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模式的目的是提供一种机制来清理托pipe资源和非托pipe资源,并在何时发生取决于如何调用Dispose方法。 在你的例子中,Dispose的使用实际上并没有做与处置有关的任何事情,因为清除清单对处理该集合没有影响。 同样,将variables设置为null的调用也不会影响GC。
你可以看看这篇文章 ,了解更多关于如何实现Dispose模式的细节,但是基本上看起来像这样:
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),它实际上在两种不同的情况下运行:
- 处置== true:该方法直接或间接由用户代码调用。 pipe理和非托pipe资源可以处置。
- 处置== false:该方法已由运行时从终结器中调用,并且不应引用其他对象。 只有非托pipe资源可以处置。
简单地让GC负责清理的问题在于,你无法真正控制GC何时运行一个收集周期(可以调用GC.Collect(),但是你真的不应该这么做),所以资源可能会停留比需要更长的时间。 请记住,调用Dispose()实际上并不会导致收集周期,或者以任何方式导致GC收集/释放对象; 它只是提供了更确定性地清理所使用的资源的方法,并告诉GC已经执行了这个清理。
IDisposable的全部和configuration模式并不是立即释放内存。 唯一一次调用Dispose实际上甚至有可能立即释放内存的时间是处理处置== falsescheme和操纵非托pipe资源。 对于托pipe代码,内存实际上不会被回收,直到GC运行一个您真正无法控制的收集周期(除了调用GC.Collect(),我已经提到这不是一个好主意)。
由于.NET中的string没有使用任何未经处理的资源,也没有实现IDisposable,所以你的场景并不真正有效,因此没有办法强制它们被“清理”。
在调用Dispose之后,不应再有对对象的方法的调用(尽pipe对象应该容忍对Dispose的进一步调用)。 因此,这个问题的例子是愚蠢的。 如果Dispose被调用,那么对象本身可以被丢弃。 所以用户应该丢弃对整个对象的所有引用(将它们设置为null),并且内部的所有相关对象都会自动清理。
关于托pipe/非托pipe的一般性问题和其他答案中的讨论,我认为对这个问题的任何回答都必须从非托pipe资源的定义开始。
归结起来有一个函数可以调用来使系统进入一个状态,还有另外一个函数可以调用以使其退出状态。 现在,在典型的例子中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle
的调用。
但是 – 这是关键 – 它们可以是任何匹配的function对。 一个build立一个国家,另一个把它撕下来。 如果状态已经build立但尚未拆除,则存在资源的实例。 你必须安排在合适的时间进行拆卸 – 资源不由CLRpipe理。 唯一自动pipe理的资源types是内存。 有两种:GC和堆栈。 值types由堆栈pipe理(或通过在引用types内部搭build),引用types由GCpipe理。
这些函数可能会导致可以自由交错的状态更改,也可能需要完全嵌套。 状态改变可能是线程安全的,或者他们可能不是。
看正义的问题的例子。 日志文件缩进的更改必须完全嵌套,否则一切都会出错。 他们也不太可能是线程安全的。
用垃圾收集器搭便车可以清理你的非托pipe资源。 但是只有当状态改变函数是线程安全的,而且两个状态可以有任何重叠的生命周期。 所以正义的资源的例子不能有一个终结者! 它只是不会帮助任何人。
对于这些资源,您可以实现IDisposable
,而无需终结器。 终结者是绝对可选的 – 它必须是。 这是掩盖或甚至没有提到许多书籍。
然后,您必须使用using
语句来确保Dispose
被调用。 这本质上就像搭上栈(所以终结器是GC, using
是堆栈)。
缺less的部分是你必须手动编写Dispose,并把它调用到你的字段和你的基类。 C ++ / CLI程序员不必这样做。 编译器在大多数情况下为它们编写它。
还有一个替代scheme,我更喜欢那些嵌套完美的状态,而不是线程安全的(除了别的之外,避免使用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
一样的目的,除了你不再有主叫者滥用它的危险之外。 他们不可能无法清理资源。
如果资源是可能具有重叠生命期的那种,那么这种技术就没那么有用了,因为那样你就可以build立资源A,然后build立资源B,然后终止资源A,然后终止资源B.你不能这么做如果你迫使用户像这样完美的嵌套。 但是,那么你需要使用IDisposable
(但仍然没有终结者,除非你已经实现了threadsafety,这不是免费的)。
我使用IDisposablescheme:清理非托pipe资源,取消订阅事件,closures连接
我用来实现IDisposable( 不是线程安全 )的习惯用法:
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 }
如果MyCollection
将被垃圾回收,那么你不应该处置它。 这样做只会使CPU超出需要的程度,甚至可能会使垃圾收集器已经执行的一些预先计算的分析失效。
我使用IDisposable
来做一些事情,比如确保线程正确处理,以及非托pipe资源。
编辑为了回应斯科特的评论:
GC性能指标受影响的唯一时间是调用[sic] GC.Collect()时的“
从概念上讲,GC维护对象引用图的视图,以及从线程的堆栈帧引用它的所有引用。 这个堆可能相当大,并跨越多页的内存。 作为一种优化,GCcaching对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。 GC在页面中的数据发生更改时收到来自内核的通知,因此它知道该页面很脏并且需要重新扫描。 如果集合在Gen0中,那么页面中的其他内容也可能会发生变化,但在Gen1和Gen2中可能性较小。 有趣的是,这些钩子在Mac OS X中不适用于将GC移植到Mac的团队,以便让Silverlight插件在该平台上工作。
还有一点反对不必要的资源处理:想象一个stream程卸载的情况。 想象一下,这个过程已经运行了一段时间了。 很有可能这个进程的许多内存页面已经交换到磁盘。 至less它们不再处于L1或L2caching中。 在这种情况下,正在卸载的应用程序将所有这些数据和代码页交换回内存以释放由操作系统在进程终止时将释放的资源。 这适用于托pipe甚至某些非托pipe资源。 仅保留非后台线程的资源必须处于活动状态,否则该进程将保持活动状态。
现在,在正常执行过程中,必须正确清理临时资源(如@fezmonkey指出数据库连接,套接字,窗口句柄 ),以避免非托pipe内存泄漏。 这些都是必须处理的东西。 如果你创build了一个拥有一个线程的类(并且拥有我的意思是它创build了它,并且因此负责确保它停止,至less通过我的编码风格),那么这个类很可能必须实现IDisposable
并且在Dispose
。
.NET框架使用IDisposable
接口作为一个信号,甚至警告开发人员这个类必须被丢弃。 我不能想到在实现IDisposable
的框架中的任何types(不包括显式接口实现),其中处置是可选的。
Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear()
calls.
Answer to your edit: Sort of. 如果我这样做:
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
It's functionally identical to this for purposes of memory management:
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
If you really really really need to free the memory this very instant, call GC.Collect()
. There's no reason to do this here, though. The memory will be freed when it's needed.
If you want to delete right now , use unmanaged memory .
看到:
- Marshal.AllocHGlobal
- Marshal.FreeHGlobal
- Marshal.DestroyStructure
I won't repeat the usual stuff about Using or freeing un-managed resources, that has all been covered. But I would like to point out what seems a common misconception.
给出以下代码
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
I realise that the Disposable implementation does not follow current guidelines, but hopefully you all get the idea.
Now, when Dispose is called, how much memory gets freed?
Answer: None.
Calling Dispose can release unmanaged resources, it CANNOT reclaim managed memory, only the GC can do that. Thats not to say that the above isn't a good idea, following the above pattern is still a good idea in fact. Once Dispose has been run, there is nothing stopping the GC re-claiming the memory that was being used by _Large, even though the instance of LargeStuff may still be in scope. The strings in _Large may also be in gen 0 but the instance of LargeStuff might be gen 2, so again, memory would be re-claimed sooner.
There is no point in adding a finaliser to call the Dispose method shown above though. That will just DELAY the re-claiming of memory to allow the finaliser to run.
In the example you posted, it still doesn't "free the memory now". All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You'd have to run some tests to be sure.
The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.
I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit
call never happened it would then call Rollback
on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:
using( MiniTx tx = new MiniTx() ) { // code that might not work. tx.Commit(); }
I've also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.
using( LogTimer log = new LogTimer("MyCategory", "Some message") ) { // code to time... }
So here are a couple of concrete examples that don't do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.
If anything, I'd expect the code to be less efficient than when leaving it out.
Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it…
Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .
I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties …
There are things that the Dispose()
operation does in the example code that might have an effect that would not occur due to a normal GC of the MyCollection
object.
If the objects referenced by _theList
or _theDict
are referred to by other objects, then that List<>
or Dictionary<>
object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.
Of course, if this were the situation I would call it a broken design – I'm just pointing out (pedantically, I suppose) that the Dispose()
operation might not be completely redundant, depending on whether there are other uses of the List<>
or Dictionary<>
that are not shown in the fragment.
One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.
Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.
An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.
IDisposable
is good for unsubscribing from events.
The most justifiable use case for disposal of managed resources, is preparation for the GC to reclaim resources that would otherwise never be collected.
A prime example is circular references.
Whilst it's best practice to use patterns that avoid circular references, if you do end up with (for example) a 'child' object that has a reference back to its 'parent', this can stop GC collection of the parent if you just abandon the reference and rely on GC – plus if you have implemented a finalizer, it'll never be called.
The only way round this is to manually break the circular references by setting the Parent references to null on the children.
Implementing IDisposable on parent and children is the best way to do this. When Dispose is called on the Parent, call Dispose on all Children, and in the child Dispose method, set the Parent references to null.
First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn't know how to deal with such objects. If class has for example only value types, then I don't consider this class as class with unmanaged resources. For my code I follow next practices:
- If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
- Clean objects as soon as I finished usage of it.
- In my dispose method I iterate over all IDisposable members of class and call Dispose.
- In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
- As additional precaution I try to make possible calling of Dispose() multiple times.
- Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
Following template demonstrates what I described in words as sample of code:
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; } }
Your given code sample is not a good example for IDisposable
usage. Dictionary clearing normally shouldn't go to the Dispose
method. Dictionary items will be cleared and disposed when it goes out of scope. IDisposable
implementation is required to free some memory/handlers that will not release/free even after they out of scope.
The following example shows a good example for IDisposable pattern with some code and comments.
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. } }