这里已经有很多很好的讨论,我参加聚会有点晚了,但我想自己补充几点。
- 垃圾收集器永远不会直接为您执行 Dispose 方法。
- GC将在需要时执行终结器。
- 用于具有终结器的对象的一种常见模式是让它调用一个按照约定定义为 Dispose(bool disposing) 的方法,传递 false 以指示调用是由于终结而不是显式的 Dispose 调用。
- 这是因为在完成对象时对其他托管对象做出任何假设是不安全的(它们可能已经完成)。
class SomeObject : IDisposable {
IntPtr _SomeNativeHandle;
FileStream _SomeFileStream;
// Something useful here
~ SomeObject() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
}
protected virtual void Dispose(bool disposing) {
if(disposing) {
GC.SuppressFinalize(this);
//Because the object was explicitly disposed, there will be no need to
//run the finalizer. Suppressing it reduces pressure on the GC
//The managed reference to an IDisposable is disposed only if the
_SomeFileStream.Dispose();
}
//Regardless, clean up the native handle ourselves. Because it is simple a member
// of the current instance, the GC can't have done anything to it,
// and this is the onlyplace to safely clean up
if(IntPtr.Zero != _SomeNativeHandle) {
NativeMethods.CloseHandle(_SomeNativeHandle);
_SomeNativeHandle = IntPtr.Zero;
}
}
}
这是简单的版本,但是有很多细微差别会让你在这种模式上绊倒。
- IDisposable.Dispose 的约定表明多次调用必须是安全的(对已释放的对象调用 Dispose 应该什么都不做)
- 正确管理一次性对象的继承层次结构可能会变得非常复杂,尤其是在不同层引入新的一次性和非托管资源时。在上面的模式中,Dispose(bool) 是虚拟的,允许对其进行覆盖以便对其进行管理,但我发现它容易出错。
在我看来,最好完全避免使用任何直接包含一次性引用和可能需要最终确定的本机资源的类型。SafeHandles 提供了一种非常简洁的方法,将本机资源封装到内部提供自己的终结处理(以及许多其他好处,例如在 P/Invoke 期间删除窗口,其中本机句柄可能由于异步异常而丢失) .
简单地定义一个 SafeHandle 使这变得微不足道:
private class SomeSafeHandle
: SafeHandleZeroOrMinusOneIsInvalid {
public SomeSafeHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{ return NativeMethods.CloseHandle(handle); }
}
允许您将包含类型简化为:
class SomeObject : IDisposable {
SomeSafeHandle _SomeSafeHandle;
FileStream _SomeFileStream;
// Something useful here
public virtual void Dispose() {
_SomeSafeHandle.Dispose();
_SomeFileStream.Dispose();
}
}