要讲清楚这个问题,首先需要了解垃圾收集器(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方法。