c# 自身对于所有托管对象(managed object)都是可以不用程序员去干预的(注:可以不用干预,当然资源耗费很多且必要时也可以去做一些干预来提升程序性能,比如通过代码部分控制垃圾回收),但对于非托管资源(文件、流、数据库连接、GDI+对象、COM对象等等)都需要程序来控制资源的释放。
释放资源主要有两种方式,其一是对象实现IDisposable接口,由程序员调用IDisposable.Dispose()方法来释放资源;其二是通过重写继承自Object对象的虚方法Finalize()来释放资源。
public interface IDisposable
{void Dispose();}
说明:垃圾回收器GC不支持IDisposable接口,不能通过自动的垃圾回收机制或手工调用GC.Collect()方法来调用Disposable方法。
重要点:只有通过程序显式或非显式调用Dispose方法,才会通过该方法释放资源。所谓显示调用,就是直接在代码中写Dispose()来带调用。关于隐式调用,常见的有两种:
(1)其一就是使用using关键字
using(StreamWriter sw= new StreamWriter(NewFilePath,true))
{
//处理完代码后,会调用Dispose方法。即使发生了异常,也会调用Dispose方法。本质上中间代码是转换为try{}finally{}的,而Dispose方法是在finally{}中调用的,所以不担心因为异常而没有调用到Dispose方法
}
(2)关于Dispose方法的隐式调用,另一种常见的是对Dispose的间接调用而已,常见的是一些类库中的别名调用。比如
FileSream fs=new FileStream("abc.txt“,FileMode.OpenOrCreate);
//dosomething
fs.close(); //close()释放资源,实质上就是隐式调用了Dispose()释放了资源。注:如果不调用close()而调用Dispose(),其效果是一样的。
要点:Finalize是由Object基类定义的虚方法;垃圾回收的时候GC会调用Finalize()方法;在自己的程序中,通过c#是不能直接重载Finalize()方法的,会出现编译错误,what?why?how?那怎么办呢?答案是:用析构函数~MyClass()来代替Finalize()方法,在析构函数中写释放资源的代码。
class MyClass
{
protected override void Finalize(){} //是不能编译的,编译不通过
//为什么会这样呢。Finalize方法本来就是Object类定义的受保护的虚方法(protected virtual),这里却不能重写???
//不知道微软是怎么处理的,但就是不能继承,咱也没有办法的,那就不要这么做了。微软要我们直接写析构函数~MyClass(){}
}
class MyClass
{
protected override void Finalize(){} //是不能编译的,编译不通过
~MyClass()
{
MyReleaseSourceCode; //在这里写释放资源的代码
}
}
经过仔细查证,发现:析构函数经过编译后,在中间代码中就会转换为Finalize()方法,所以c#的析构函数本质上就是Finalize()方法。但是,通过析构函数转换为Finalize()方法的过程中,编译器会添加一些额外的代码。通过ildasm.exe查看c#析构函数编译过的代码,可以发现编译器加入了一些错误检测代码。分析中间码可以发现,程序员自己写的代码都会放在中间代码的try{}块中,另外在中间代码的finally块中放置了一些代码,保证基类的Finalize方法总是被执行。以下是编译后的中间码部分展示,用来说明编译后的析构函数的样子。
.method family hidebysig virtual instance void Finalize() cil managed //很明显析构函数编译后就是Finalize()方法
{
.maxstack 1
.try
{
....
Compiled MyReleaseSourceCode //这个地方方式编译后的释放资源的代码
...
}
finally
{
......
IL_0014:
call instance void [mscorlib] System.Object::Finalize() //调用父类的Finalize方法
......
}
}
因此,析构函数(或者说Finalize方法)是先调用本类需要析构的资源,然后调用父类的Finalize()继续析构的,能够保证基类的资源被正确释放。
比如,以下代码:
class Animal
{
~Animal()
{
Console.WriteLine("Animal Destructed");
}
}
class Person:Animal
{
~Person()
{
Console.WriteLine("Person Destructed");
}
}
class Program
{
static void Main(string[] args)
{
Person p = new Person();
p = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.Read();
}
}
上述代码执行后,会输出:
Person Destructed
Animal Destructed
也就是先执行子类的析构函数,后执行父类的析构函数。
如果仅仅实现IDisposable方法而没有重载Finalize()(说明:重载Finalize就是定义了析构函数,因为析构函数编译后就是重载的Finalize()方法),那么如果忘了调用Dispose方法,那么非托管资源可能会永远留在内存中。
可以既实现IDisposable方法也重载Finalize()方法。如果在Dispose方法中调用GC.SuppressFinalize()通知垃圾回收器跳过终结过程,则在Dispose()后,当GC在回收的时候,就不会调用Finalize()方法中的代码。但如果用户忘记了调用 Dispose方法,那么在垃圾回收的时候就会调用释放资源的代码。
微软提供的样板代码如下:
class MyClass:IDisposable
{
private bool disposed = false;
public void Dispose()
{
DisposeImpl(true);
GC.SuppressFinalize(this);
}
private void DisposeImpl(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
//这里释放托管的资源
}
//这里释放非托管的资源
}
disposed = true;
}
~MyClass()
{
DisposeImpl(false);
}
}