在这里我主要说说自己对垃圾回收机制中两个函数的理解Finalize()和Dispose(),在.net平台中为了能够帮助程序员写出高效稳定的代码,提供了垃圾回收器(GC),使我们在使用了内存后忘记释放内存,而不必要担心会引起内存泄漏等问题,当一个应用程序在运行的时候,垃圾回收器设置了一个托管堆。托管堆和C语言中的堆向类似,但是程序员不需要从托管堆中释放对象,并且在托管堆中对象的存放是连续的。
GC负责清除在堆上分配的内存(C#语言的所有对象都在堆上使用new分配内存,应该没错吧),我们应用程序创建的大多数对象,可以依靠GC隐式执行回收不需要使用的内存,但是对于栈上分配的内存,像我们创建的非托管资源对象(常见的一类像操作系统资源对象如文件,窗口,或者调用C程序中使用了malloc分配内存时(COM)),GC就管不着了,虽然GC可以跟踪这些使用非托管资源的对象的生存周期,但是它并不了解如何清理这些资源,对于这些类型的对象,这里就开始介绍Finalize方法了,.NET Framework 提供Finalize方法,它允许对象在垃圾回收器回收该对象使用的内存时适当清理其非托管资源。默认情况下,Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,我们就需要重写这个方法,但是注意在C#中析构函数为我们提供了清理操作的机制,我们不能重写这个方法,在这里我们可能会想到能不能直接调用对象的Finalize方法,这里必须注意这是绝对容许的,除非是在子类的Finalize中调用基类的Finalize。
我们使用Finalize就是为了处理这些GC处理不了的非托管资源,但是Finalize作为CRL提供的一个机制,什么时候会调用Finalize由GC决定,根本无法控制,无法及时的释放宝贵的非托管资源,也就是说Finalize的执行是无预料性的,所以在这里我们可以使用Disponse方法,它不是CLR提供的一个机制,仅仅是一个设计模式(实现IDisponsable接口),目的是使用完类对象后,可以及时手动清除非托管资源的释放,无须等待GC回收的那个时间点。说到这里应该下面这段代码能看懂了:
public class Books : IDisposable //实现IDisponsable接口
{
private SqlDataAdapter dsCommand;
public Books()
{
dsCommand = new SqlDataAdapter();
dsCommand.SelectCommand = new SqlCommand();
dsCommand.SelectCommand.Connection=new SqlConnection(DuwamishConfiguration.ConnectionString);
dsCommand.TableMappings.Add("Table", BookData.BOOKS_TABLE);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); //告诉GC从终结列中清除自己,不需要再次处理Finalize方法,以提供系统性能。
}
protected virtual void Dispose(bool disposing)
{
if (! disposing)
{释放非托管资源;
}
if (dsCommand != null )
{
if (dsCommand.SelectCommand != null)
{
if( dsCommand.SelectCommand.Connection != null)
dsCommand.SelectCommand.Connection.Dispose();
dsCommand.SelectCommand.Dispose();
}
dsCommand.Dispose();
dsCommand = null;
}
}
像上面这个类,如果客户调用了Disponse(),就能及时释放托管和非托管资源,当该对象回收时,就不会执行Finalize;但是如果客户没有调用Disponse(),那吗在垃圾回收时,就不能保证会释放非托管资源了,因为没有为类定义Finalize方法,所以我们可以加个析构函数,
~Books()
{
Disponse(false);
}
这样就能保证当到了GC回收点时也会释放非托管资源。到了这里我们又为什么要用Disponse(bool a)呢,为什么不直接使用Disponse()就得了,但是我们要知道为了释放非托管资源资源,我们必须要搞清楚是GC自动调用了Finalize还是客户调用了Disponse(),但是又有人说为什么不把非托管资源和托管资源放到Finalize中处理,(1,我们是为了能手动清除资源,如果在Finalize我们根本不能保证什么时候才会被GC调用,2,如果在Finalize中清除托管资源的话,正是由于GC调用Finalize的无时间性,我们建议不在Finalize中释放托管资源,这样会引发一些不可预料的异常,如果我们在Finalize中释放的对象引用了其他托管资源对象,而这些托管资源对象可能这时候已经被回收了,这样将导致一个无法预知的结果,强烈建议不要在Finalize中引用其他的托管对象)。其中使用了 protected virtual void Dispose(bool disposing) 和public void Dispose(),前者也只是为了保证外部不能被调用,因为根本不能保证外部会调用Disponse(false),因为已经被Finalize使用了,所以只向外部提供了public void Dispose()。
下面是我们用到的的定义的模式
public class Class1 : IDisposable
{
public Class1()
{
}
~Class1 ()
{
//垃圾回收器将调用该方法,因此参数需要为false。
Dispose (false);
}
//该方法定义在IDisposable接口中。
public void Dispose ()
{
//该方法由程序调用,在调用该方法之后对象将被终结。
//因为我们不希望垃圾回收器再次终结对象,因此需要从终结列表中去除该对象。
GC.SuppressFinalize (this);
//因为是由程序调用该方法的,因此参数为true。
Dispose (true);
}
//所有与回收相关的工作都由该方法完成
private void Dispose(bool disposing)
{
lock(this) //避免产生线程错误。
{
if (disposing)
{
//需要程序员完成释放对象占用的资源。
}
//对象将被垃圾回收器终结。在这里添加其它和清除对象相关的代码。
}
}
}