有两个可选的方法来释放非托管代码所占用的资源
1:运行时强迫执行析构器,但是你无法确定执行的时间,这是由垃圾收集器的工作原理所决定的。
2:IDisposable接口提供了一个机制,它允许类的用户通过调用Dispose()方法来决定何时释放资源。
一般来说,最好的方法是同时实现这两个机制,因为这样就可以互相克服各自的缺点。假如大多数的程序员都可以正确的调用Dispose()方法,你就可以实现IDisposable接口,同时要考虑到如果没有正确调用Dispose()方法,保证代码安全就需要提供一个析构器。下面是一个同时实现IDisposable和析构器例子。
namespace Chengbo {
class MyClass {
public static void Main() {
ResourceHolder r1 = new ResourceHolder();
Console.WriteLine("IDisposable");
r1.Dispose();
ResourceHolder r2 = new ResourceHolder();
Console.WriteLine("Garbage Collector");
GC.Collect();
}
}
public class ResourceHolder : IDisposable {
private bool isDisposed;
public void Dispose() {
Dispose(true);
//请求系统不要调用指定对象的完成器方法。
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if(!isDisposed) {
if(disposing) {
Console.WriteLine("Dispose the manage object");
}
Console.WriteLine("Dispose the unmanage object");
}
isDisposed = true;
}
~ResourceHolder() {
Dispose(false);
}
}
}
可以看到,Dispose()有个受保护的重载方法,它接受一个bool参数,执行所有的清理工作,同时被析构器和IDisposable.Dispose()调用。这样做的要点就是保证所有的清理代码都在一处。
传递给Dispose(bool)方法的参数指出到底是IDisposable.Dispose()还是析构器在调用Dispose(bool)方法。在代码的其它任何地方都不能调用Dispose(bool)方法,因为:
1:如果用户调用IDisposable.Dispose(),那么所有的托管和与对象有关的非托管资源都会被清除。
2:如果析构器被调用,那么所有的资源依旧需要清除。但是在这种情况下,我们知道析构器是被垃圾收集器调用的,因为我们不再确定其它托管对象的状态,所以我们并不应该尝试访问它们。我们最好是清除已知的非托管资源,同时希望所有引用的托管对象包含他们自己的析构器来执行自身的垃圾收集。
成员变量isDisposed表示对象是否已经被Dispose,从而保证我们不多次尝试Dispose它。这个简单的方法不是线程安全的(thread-safe),我们只能期望调用者确定调用时只有一个线程。要求用户强制同步是一个合理的假定,这在.NET类库里再三被用到(比如在Collection类中)。
最后,IDisposable.Dispose()调用了System.GC.SuppressFinalize()方法。GC是一个被设计成描述垃圾收集器的类,SuppressFinalize()方法告诉垃圾收集器不需要调用析构器。因为执行Dispose()后就已经做了需要的所有清除工作,没有任何事留给析构器去做了。调用SuppressFinalize()方法也就是说垃圾收集器就像根本就没析构器一样的处理这个类。