析构函数与终结操作
创建对象的时候会调用构造函数初始化实例信息,当然析构函数就是释放对象时做的一些释放操作。
为什么需要析构函数,普遍来说,因为我们的对象中可能用到了一些非托管的代码,譬如数据库操作,网络,本地文件等等,这些资源
不是托管的,所以需要我们的托管对象在销毁时同事释放那些之前使用到的非托管对象,否则一直未关闭,释放,就可能导致泄漏。
如果你的对象中根本没用到这些非托管资源,那么请不要定义析构函数,毕竟有代价的。
1、析构函数被编译后,实际变成了Finalize()方法,所以实际上C#中也不允许你自己定义一个Finalize方法
2、.NET中的托管对象回收,都是通过垃圾回收机制来实现的,不用我们手动就释放。当CLR创建对象的时候,如果发现你定义了析构函数,也就是(Finalize方法),
那么创建好对象的时候,会将对象地址另外放到一个“终结队列”上;运行一点时间后,对象在根中无法找到引用,且垃圾回收准备释放该对象的时候,发现其在终结队列有引用,
则将其引用移动到“终结可达队列”,也就是说一般如果是没有析构方法的对象,此时就被回收了,但是有析构函数的对象,则不会被回收,只是将引用移动到“终结可达队列“。
然后程序将继续运行,当然CLR发现”终结可达队列“有几个对象的引用存在,则启动一个单独的线程,来遍历队列中的对象,执行其析构函数,并将引用从”终结可达队列“清除。
Ok,这时譬如运行一点时间后,垃圾回收操作又开始清理了,这时候在回收其它垃圾对象的同时,会把之前”终结可达队列“上的那些对象一并清除。
也就是说有析构函数的对象,实际上要执行两次的垃圾回收操作,才能释放内存。所有有析构函数的对象时有代价的,但是又是必须的,毕竟确实需要释放一些非托管的资源。
3、通过上面的那些过程了解到,析构函数无非是为了当对象销毁之前去做一些非托管资源的释放,但却因此提高了释放的代价,
那么假如我们知道某个对象不需要非托管资源了,那么其实就可以手动去释放,不比等到垃圾回收的时候去释放。因此.NET提供了一个Dispose形式,调用Dispose来手动释放非托管资源。但是嘛,析构函数还是会照样保存,以免有些情况忘记了去释放,毕竟析构函数是是CLR调用的。也许你会想,那么就算手动调用Dispose释放了非托管资源,那么
对象要回收的时候,CLR同样调用了析构函数,那么我们的Dispose有什么意义呢?实际上通常提供了Dispose形式的, 再Dispose方法内部的最后通常有那么一句
GC.SupproseFinalize(this),也就是告诉CLR:好了,我已经自己去释放非托管资源了,不需要你来释放了,在垃圾回收的时候,你直接把对象回收了,不要给我再来添加到
“终结可达队列”了。所以这样当已经调用过Dispose操作的,就不会出现2次回收才释放对象的情况。
通常类似下面这样的代码
public class MyClass:IDispose { ~MyClass(){ Dispose(false);//这个析构器是CLR调用的,默认还是写上,防止未手动释放时,CLR可以把非托管释放 } public void Close(){ Dispose(true); } public void Dispose(){ Dispose(true); } private void Dispose(bool bool1){ if(bool1){ //写一点自己能控制的代码 } //释放非托管资管 GC.SupposeFinalize(this)//告诉CLR,释放过了,不要再添加到“终结可达队列”,直接销毁内存 } }