老生常谈的问题了,MSDN也有非常详细的说明但看起来不是很系统。也曾经做过分析,但没有总结下来又忘了,这次整理一下MSDN和网上搜集的一些资料,以备不时只需。
下面是MSDN对这两个函数的建议使用方法
1 MSDN建议 2 // Design pattern for a base class. 3 public class Base : IDisposable 4 { 5 //保证重复释放资源时系统异常 6 private bool _isDisposed = false; 7 8 // 析构函数,编译器自动生成Finalize()函数由GC自动调用,保证资源被回收。 9 // 最好不要声明空析构函数,造成性能问题 10 // 如果没有引用非托管资源就不需要显示声明析构函数,会造成性能问题,系统会自动生成默认析构函数 11 ~Base() 12 { 13 // 此处只需要释放非托管代码即可,因为GC调用时该对象资源可能还不需要释放 14 Dispose(false); 15 } 16 17 //外部手动调用或者在using中自动调用,同时释放托管资源和非托管资源 18 public void Dispose() 19 { 20 Dispose(true); 21 GC.SuppressFinalize(this); ///告诉GC不需要再次调用 22 } 23 24 protected virtual void Dispose(bool disposing) 25 { 26 if (!_isDisposed) 27 { 28 if (disposing) 29 { 30 //释放托管资源 31 } 32 // 释放非托管资源 33 // 释放大对象 34 35 this._isDisposed = true; 36 } 37 38 } 39 40 }
下面是通过Reflector工具对上面代码反射出来的结果,可以看出析构函数直接被翻译成Finalize()函数了,因为Finalize函数不能被重写,所以只能用析构函数的方式实现Finalize方法。
1 Reflector反射结果 2 public class Base : IDisposable 3 { 4 // Fields 5 private bool _isDisposed; 6 7 // Methods 8 public Base(); 9 public void Dispose(); 10 protected virtual void Dispose(bool disposing); 11 protected override void Finalize(); 12 }
在.NET的对象中实际上有两个用于释放资源的函数:Dispose和Finalize。Finalize的目的是用于释放非托管的资源,而Dispose是用于释放所有资源,包括托管的和非托管的。
在这个模式中,void Dispose(bool disposing)函数通过一个disposing参数来区别当前是否是被Dispose()调用。如果是被Dispose()调用,那么需要同时释放 托管和非托管的资源。如果是被~Base()(也就是C#的Finalize())调用了,那么只需要释放非托管的资源即可。
这是因为,Dispose()函数是被其它代码显式调用并要求释放资源的,而Finalize是被GC调用的。在GC调用的时候Base所引用的其它托管对象可能还不需要被销毁,并且即使要销毁,也会由GC来调用。因此在Finalize中只需要释放非托管资源即可。另外一方面,由于在 Dispose()中已经释放了托管和非托管的资源,因此在对象被GC回收时再次调用Finalize是没有必要的,所以在Dispose()中调用 GC.SuppressFinalize(this)避免重复调用Finalize。
然而,即使重复调用Finalize和Dispose也是不存在问题的,因为有变量_isDisposed的存在,资源只会被释放一次,多余的调用会被忽略过去。因此,上面的模式保证了:
1、 Finalize只释放非托管资源;
2、 Dispose释放托管和非托管资源;
3、 重复调用Finalize和Dispose是没有问题的;
4、 Finalize和Dispose共享相同的资源释放策略,因此他们之间也是没有冲突的。
微软对Dispose和Finalize方法使用准则
Finalize
下面的规则概括了 Finalize 方法的使用准则:
1、不能在结构中定义析构函数。只能对类使用析构函数。
2、一个类只能有一个析构函数。
3、无法继承或重载析构函数。
4、无法调用析构函数。它们是被自动调用的。
5、析构函数既没有修饰符,也没有参数。
注意
基类的 Finalize 方法通过 C# 和 C++ 析构函数语法自动进行调用。
释放
下面的规则概括了 Dispose 方法的使用准则:
1、Finalize方法(C#中是析构函数,以下称析构函数)是用于释放非托管资源的,而托管资源会由GC自动回收。所以,我们也可以这样来区分 托管和非托管资源。所有会由GC自动回收的资源,就是托管的资源,而不能由GC自动回收的资源,就是非托管资源。在我们的类中直接使用非托管资源的情况很 少,所以基本上不用我们写析构函数。
2、大部分的非托管资源会给系统带来很多负面影响,例如数据库连接不被释放就可能导致连接池中的可用数据库连接用尽。文件不关闭会导致其它进程无法读写这个文件等等。
实现模型:
1、由于大多数的非托管资源都要求可以手动释放,所以,我们应该专门为释放非托管资源公开一个方法。实现IDispose接口的Dispose方法是最好的模型,因为C#支持using语句快,可以在离开语句块时自动调用Dispose方法。
2、虽然可以手动释放非托管资源,我们仍然要在析构函数中释放非托管资源,这样才是安全的应用程序。否则如果因为程序员的疏忽忘记了手动释放非托管资源, 那么就会带来灾难性的后果。所以说在析构函数中释放非托管资源,是一种补救的措施,至少对于大多数类来说是如此。
3、由于析构函数的调用将导致GC对对象回收的效率降低,所以如果已经完成了析构函数该干的事情(例如释放非托管资源),就应当使用SuppressFinalize方法告诉GC不需要再执行某个对象的析构函数。
4、析构函数中只能释放非托管资源而不能对任何托管的对象/资源进行操作。因为你无法预测析构函数的运行时机,所以,当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。这样将导致严重的后果。
5、(这是一个规则)如果一个类拥有一个实现了IDispose接口类型的成员,并创建(注意是创建,而不是接收,必须是由类自己创建)它的实例对象,则 这个类也应该实现IDispose接口,并在Dispose方法中调用所有实现了IDispose接口的成员的Dispose方法。
只有这样的才能保证所有实现了IDispose接口的类的对象的Dispose方法能够被调用到,确保可以手动释放任何需要释放的资源。
————————————————————————————————————————
一个人的时候,总是在想
我的生活到底在期待什么……