谈IDisposable模式

今天看了“生鱼片”的.net中IDisposable接口这篇文章,发现作者的理解跟我的理解有相当的差异,在此谈谈我对IDisposable接口及Disposable模式的理解。

要讲清楚这个问题,首先需要了解垃圾收集器(GC)的工作原理,这里我分以下几步来简单描述GC回收垃圾的过程:
1、GC发现一个对象(该对象不可达)已经成为垃圾,需要回收;
2、GC查看该对象(或父对象)是否重写了Finalize方法(忽略继承自object.Finalize方法),如果没有,则直接回收该对象的内存,如果有,则GC将该对象移到它内部维护的一个队列(终结可达队列)中,从而使该对象再次可达(称为“复苏”,注意该队列的名字“终结可达”)。
3、当该终结可达队列非空时,GC会唤醒一个线程,该线程对该队列的每个对象执行Finalize方法,并将该对象从队列中移除,从而使该对象再次死亡。
4、再次运行垃圾收集的GC发现该对象彻底死亡,回收该对象的内存。

因此,根据以上的分析,如果该对象包含非托管资源,但正确实现了Finalize方法,那么,GC就能正确释放该非托管资源,不存在内存泄露或无法释放非托管资源的问题。

那么,既然如此,为什么还要提供IDisposable接口呢?原因是,虽然Finalize方法能正确释放非托管资源,但是Finalize方法的调用是非确定的(该方法不能被用户调用),而很多时候,确定性的释放资源,及早的释放资源是很有用的,因此IDisposable接口的作用是为了给程序员提供确定性的释放资源的能力(用户可以确定的调用Dispose方法)。

所以,“生鱼片”提出的以下的写法,我认为是错误的,因为Dispose方法是给用户调用的,而不是给Finalize方法调用的。
public class CaryClass: IDisposable
{
~CaryClass()
{
Dispose();
}
public void Dispose()
{
// 清理资源
}
}
 
接下来,我们来分析一下Dispose模式,代码如下:
private bool IsDisposed=false
public void Dispose() 

     Dispose(true); 
     GC.SupressFinalize(this); 

protected void Dispose(bool Diposing) 

     if(!IsDisposed) 
     { 
         if(Disposing) 
         { 
            //代码1
         } 
         //代码2
     } 
     IsDisposed=true

~CaryClass() 

     Dispose(false); 
}

我们发现,Dispose方法与Finalize方法调用的是同一个方法(带一个bool参数的Dispose方法),这是容易理解的,因为它们的工作是一样的,都是释放资源。但,我们发现,Dispose方法比Finalize方法多执行了“代码1”这部分内容,这是为什么呢?为什么Finalize方法不能执行“代码1”这部分代码呢?

我们先来看一看的IDE自动生成继承自Form的窗口类的代码里的Dispose(bool disposing)方法
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
我们发现,这里对应的“代码1”这部分的代码为components.Dispose(),而components这个对象是Form窗体类的一个字段,也就是该Form窗口的一个实现了IDispose接口的一个成员。我们知道,如果该窗体要被销毁,那么该窗体的成员也应该被销毁,这是不言而喻的,因此,调用components.Dispose()方法是理所应当的。那同样是销毁窗体,为什么Finalize方法就不调用components.Dispose()方法呢?是不需要,还是不能?

答案是不能。这里还得从Dispose与Finalize这两个方法针对不同的调用者来说起,我们要记住,Dispose是针对用户来调用的,而Finalize是给GC来调用的,正是这个差异导致了Finalize方法不能调用components.Dispose()方法。

我们设想一下这样一种情况,如果Finalize方法也调用components.Dispose()方法,那么当GC首次发现该Form窗体不可达时,GC同时也会发现components不可达(被不可达的对象可达的不算可达),因此,GC就会将这两个对象都移到终结可达队列,然后,GC的一个线程调用该队列中的对象的Finalize方法,该调用是无序的,即GC有可能先调用Form的Finalize方法,再调用components的Finalize方法,也可能先调用components的Finalize方法,再调用Form的Finalize方法。如果发生前一种情况,那么components就会被终结两次,而如果发生后一种情况,那么在调用Form的Finalize方法里的components.Dispose方法时,发现该对象已经被终结,已经不存在了。

而用户调用Dispose方法则不会发生这种情况,因为,调用该方法时,则Form对象一定存在,当然,Form的components也一定存在。

总结:
    1、正确实现Finalize方法就能够保证非托管资源得到正确释放,内存泄露不会发生。
    2、实现IDisposable的唯一理由就是为了确定性的控制资源的释放,弥补Finalize不能确定性的被调用的不足。
    3、实现Dispose模式时,一定要注意,Dispose方法中需要调用各个成员的Dispose方法,Finalize方法不能调用各个成员的Dispose方法。


posted @ 2008-06-16 21:46  草船上的稻草人  阅读(2957)  评论(13编辑  收藏  举报