老生常谈的问题了,MSDN也有非常详细的说明但看起来不是很系统。也曾经做过分析,但没有总结下来又忘了,这次整理一下MSDN和网上搜集的一些资料,以备不时只需。
下面是MSDN对这两个函数的建议使用方法
1 // Design pattern for a base class. 2 public class Base : IDisposable 3 { 4 //保证重复释放资源时系统异常 5 private bool _isDisposed = false; 6 7 // 析构函数,编译器自动生成Finalize()函数由GC自动调用,保证资源被回收。 8 // 最好不要声明空析构函数,造成性能问题 9 // 如果没有引用非托管资源就不需要显示声明析构函数,会造成性能问题,系统会自动生成默认析构函数 10 ~Base() 11 { 12 // 此处只需要释放非托管代码即可,因为GC调用时该对象资源可能还不需要释放 13 Dispose(false); 14 } 15 16 //外部手动调用或者在using中自动调用,同时释放托管资源和非托管资源 17 public void Dispose() 18 { 19 Dispose(true); 20 GC.SuppressFinalize(this); ///告诉GC不需要再次调用 21 } 22 23 protected virtual void Dispose(bool disposing) 24 { 25 if (!_isDisposed) 26 { 27 if (disposing) 28 { 29 //释放托管资源 30 } 31 // 释放非托管资源 32 // 释放大对象 33 34 this._isDisposed = true; 35 } 36 37 } 38 39 }
下面是通过Reflector工具对上面代码反射出来的结果,可以看出析构函数直接被翻译成Finalize()函数了,因为Finalize函数不能被重写,所以只能用析构函数的方式实现Finalize方法。
在.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。存在与 Finalize 方法相关的性能开销。
- 如果需要 Finalize 方法,应考虑实现 IDisposable,以使类的用户可以避免因调用 Finalize 方法而带来的开销。
- 不要提高 Finalize 方法的可见性。该方法的可见性应该是 protected,而不是 public。
- 对象的 Finalize 方法应该释放该对象拥有的所有外部资源。此外,Finalize 方法应该仅释放由该对象控制的资源。Finalize 方法不应该引用任何其他对象。
- 不要对不是对象的基类的对象直接调用 Finalize 方法。在 C# 编程语言中,这不是有效的操作。
- 应在对象的 Finalize 方法中调用基类的 Finalize 方法。
注意
基类的 Finalize 方法通过 C# 和 C++ 析构函数语法自动进行调用。
释放
下面的规则概括了 Dispose 方法的使用准则:
- 在封装明确需要释放的资源的类型上实现释放设计方案。用户可以通过调用公共 Dispose 方法释放外部资源。
- 在通常包含控制资源的派生类型的基类型上实现释放设计方案,即使基类型并不需要也如此。如果基类型有 Close 方法,这通常指示需要实现 Dispose。在这类情况下,不要在基类型上实现 Finalize 方法。应该在任何引入需要清理的资源的派生类型中实现 Finalize。
- 使用类型的 Dispose 方法释放该类型所拥有的所有可释放资源。
- 对实例调用了 Dispose 后,应通过调用 GC.SuppressFinalize 方法禁止 Finalize 方法运行。此规则的一个例外是当必须用 Finalize 完成 Dispose 没有完成的工作的情况,但这种情况很少见。
- 如果基类实现了 IDisposable,则应调用基类的 Dispose 方法。
- 不要假定 Dispose 将被调用。如果 Dispose 未被调用,也应该使用 Finalize 方法释放类型所拥有的非托管资源。
- 当资源已经释放时,在该类型上从实例方法(非 Dispose)引发一个 ObjectDisposedException。该规则不适用于 Dispose 方法,该方法应该可以在不引发异常的情况下被多次调用。
- 通过基类型的层次结构传播对 Dispose 的调用。Dispose 方法应释放由此对象以及此对象所拥有的任何对象所控制的所有资源。例如,可以创建一个类似 TextReader 的对象来控制 Stream 和 Encoding,两者均在用户不知道的情况下由 TextReader 创建。另外,Stream 和 Encoding 都可以获取外部资源。当对 TextReader 调用 Dispose 方法时,TextReader 应继而对 Stream 和 Encoding 调用 Dispose,使它们释放其外部资源。
- 考虑在调用了某对象的 Dispose 方法后禁止对该对象的使用。重新创建已释放的对象是难以实现的方案。
- 允许 Dispose 方法被调用多次而不引发异常。此方法在首次调用后应该什么也不做。
下面是CSDN高手总结
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方法能够被调用到,确保可以手动释放任何需要释放的资源。