Dispose模式是.NET中很基础也很重要的一个模式,今天重新复习一下相关的东西并记录下来。
什么是Dispose模式?
要知道什么是Dispose模式必须先了解两个概念:资源和GC的基本原理。资源指的是一些类似于Windows句柄,数据库连接的非内存的一些本地资源,这些资源无法被运行时本身管理,需要程序员自己去申请和释放。当一些托管类型中对这些资源进行一定的封装的时候,我们就需要妥善的去处理他们,比如说在不用的时候释放到这些资源以免造成资源泄露。但是你怎么知道什么时候不用呢?无法知道,这个只有使用这个类型的人知道,所以我们需要在Finalize方法中对资源进行释放,这个有点像C++中的析构函数,但是不一样的是.NET中的Finalize方法是由运行时来调用而非用户。
扯了这么多,貌似只要在Finalize方法中释放掉资源就行了,那Dispose模式又是干嘛的?
正如上面提到的,Finalize方法只能由运行时来调用,用户无法控制什么时候这个方法会被调用。了解一下GC的基本工作原理,我们会发现具有Finalize方法的垃圾对象会在垃圾回收的时候放入一个“垃圾队列”,但是这个垃圾队列并不是说下一次垃圾回收的时候就会调用,而是会在下一次内存压缩(compact)的时候才会去调用。[1]你不知道这货啥时候才会去调用那个Finalize方法,也就是说很可能你这个对象会存活很久(占用内存),并且这个资源一直得不到释放(占用资源)!
我写了下面这个简单的代码做了一下测试,只是为了验证一下并不是多次GC就会去调用Finalize方法的。
internal class Resource { public void DoSomething() { } ~Resource() { Console.WriteLine("In Finalize"); } } internal class Program { private static void Main(string[] args) { var res = new Resource(); res.DoSomething(); int i = 100; while (i-- > 0) { GC.Collect(); } Console.Read(); } }
那么既然Finalize方法的调用无法控制,于是就引入了Dispose模式,实现IDispose方法来提供一个Dispose方法允许调用者手动去调用Dispose方法释放资源。
什么时候我们该为一个类型实现Dispose模式
我们已经大概知道什么是Dispose模式了,那么我们应该在什么时候去为我们的类型实现这种模式呢?在以下这几种情况中我们应该为一个类型实现Dispose模式(非完全列表哦)
1. 类型对一些本地资源进行封装,比如类型暴露了一些文件操作的接口,并在内部对文件进行了操作。
2. 如果类型内部有一个Dispose成员,你需要保证这个成员必须在你的对象的生命周期内得到释放。比如说我实现了一个记日志的类,类的内部有一个成员变量是FileStream,我就需要实现Dispose模式来保证这个FileStream能够被释放。
3. 如果你要实现一个基类,基类本身没有本地资源。但是他的子类有那么必须要实现Dispose模式,典型例子就是Stream。[2]
当你在考虑为一个基类实现或者不实现Dispose模式的时候需要特别小心,可能会存在一个版本问题以后改动的时候比较麻烦。比如说,一个叫做BaseA的类开始的时候没有实现IDispose接口,但是过了一段时间以后需要实现这个接口,那么就需要找出所有继承自这个BaseA的内在他们那里实现IDispose方法并且调用基类的Dispose方法。如果不全部修改的话,可能会出现子类总的Dispose方法没有去调用基类中的Dispose方法导致基类中的资源得不到释放。另一种相反的情况也类似,比如说第一个版本的基类实现了,以后如果以后要去掉的话也必须将所有子类中调用基类的地方去掉。
使用Dispose模式时应该注意什么?
Dispose Pattern是篇好文章,好好读一下肯定有收获。下面只是摘录自这个文章外加一点点自己的东西,纯属个人翻译练习,推荐看原文。
怎么实现一个Dispose模式
参考MSDN[2],我这里找了一个.NET提供的类型做例子来看一下。FileStream。我将一些关键代码扣出来说明一下。
public abstract class Stream : IDisposable { /// <summary>Closes the current stream and releases any resources (such as sockets and file handles) /// associated with the current stream.</summary> public virtual void Close() { //调用带参的Dispose方法来清理资源,传入true表示是显示调用Dispose方法 //若子类重载则会调用子类的Dispose方法 this.Dispose(true); //告诉GC不需要执行本对象的Finalize方法 GC.SuppressFinalize(this); } /// <summary>Releases all resources used by the <see cref="T:System.IO.Stream" />.</summary> public void Dispose() { this.Close(); } /// <summary>Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> /// and optionally releases the managed resources.</summary> /// <param name="disposing">true to release both managed and unmanaged resources; /// false to release only unmanaged resources.</param> //dispose为ture释放托管和非托管的资源,因为是显示调用,其他对象的字段可以安全访问并释放。 //dispose为false的时候只释放非托管资源,因为托管的一些对象可能已经被释放掉了。 protected virtual void Dispose(bool disposing) { if (disposing && this._asyncActiveEvent != null) { this._CloseAsyncActiveEvent(Interlocked.Decrement(ref this._asyncActiveCount)); } } } public class FileStream : Stream { private SafeFileHandle _handle; //重载父类中带参的Dispose方法 protected override void Dispose(bool disposing) { try { //判断是否已经被释放,不多次释放 if (this._handle != null && !this._handle.IsClosed && this._writePos > 0) { this.FlushWrite(!disposing); } } finally { //判断是否已经被释放,不多次释放 if (this._handle != null && !this._handle.IsClosed) { this._handle.Dispose(); } this._canRead = false; this._canWrite = false; this._canSeek = false; base.Dispose(disposing); } } //垃圾回收时候调用这个方法释放资源 ~FileStream() { if (this._handle != null) { //传入false表示是Finalize方法中调用 this.Dispose(false); } } public virtual void Flush(bool flushToDisk) { //判断是否已经被释放,如果已经被释放则会抛出异常 if (this._handle.IsClosed) { //抛出 ObjectDisposedException 异常 __Error.FileNotOpen(); } //此处省略N行代码 } }
Dispose模式的例子
什么你是来找现成的代码的?Google去!
Reference:
1. Jeffrey Richter, CLR Via C#, 3rd Edition.
2. MSDN, Dispose Pattern, http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx