我不会对自己进行对象调用Dispose,但如果有必要,我当然会让构造函数自行清理。我也想让清理工作尽可能简单考虑到你的例子,我更喜欢这样写:
internal sealed class Library : IDisposable
{
IntPtr _libPtr; // Or better yet, can we use or derive from SafeHandle?
public Library(string dllPath)
{
_libPtr = NativeMethods.LoadLibrary(dllPath);
if(_libPtr == IntPtr.Zero)
{
GC.SuppressFinalize(this);
throw new DllNotFoundException("Library Load Failed");
}
}
private void Release()
{
if(_libPtr != IntPtr.Zero)
NativeMethods.FreeLibrary(_libPtr);
_libPtr = IntPtr.Zero; // avoid double free even if a caller double-disposes.
}
public void Dispose()
{
Release();
GC.SuppressFinalize(this);
}
~Library()
{
Release();
}
public IntPtr GetProcAddress(string functionName)
{
if(_libPtr == IntPtr.Zero)
throw new ObjectDisposedException();
IntPtr funcPtr = NativeMethods.GetProcAddress(_libPtr, functionName);
if(_funcPtr == IntPtr.Zero)
throw new Exception("Error binding function.");
return _funcPtr;
}
}
到目前为止很好很简单。该对象要么构造成功并且可以由调用它的代码释放,要么不需要清理。我们甚至可以防止无操作完成,只是为了好点。最重要的是,在上一件可能合理出错的事情之后,没有任何需要清理的东西。
然后:
public sealed class MyClass : IDisposable
{
private readonly Library _lib;
private readonly IntPtr _funcPtr;
public MyClass(string dllPath)
{
_lib = new Library(dllPath); // If this fails, we throw here, and we don't need clean-up.
try
{
_funcPtr = _libPtr.GetProcAddress("MyFunction");
}
catch
{
// To be here, _lib must be valid, but we've failed over-all.
_lib.Dispose();
throw;
}
}
public void Dispose()
{
_lib.Dispose();
}
// No finaliser needed, because no unmanaged resources needing finalisation are directly held.
}
再次,我可以确保清理,但我不调用 this.Dispose(); 虽然 this.Dispose() 可以做同样的伎俩,我主要更喜欢在相同的方法中显式地清理我正在清理的字段(构造函数这里)设置它但未能完成所有工作。一方面,可以有部分构造对象的唯一地方是在构造函数中,所以我需要考虑部分构造对象的唯一地方是在构造函数中;我已经使它成为_lib 不为空的类的其余部分的不变量。
让我们假设函数必须与库分开发布,只是为了有一个更复杂的例子。然后我还会包装_funcPtr 以保持简化规则;要么一个类有一个通过Dispose() 和终结器清理的非托管资源,要么它有一个或多个通过Dispose 清理的IDisposable 字段,或者它不需要处理,但绝不是组合以上。
internal sealed class Function : IDisposable
{
IntPtr _funcPtr; // Again better yet, can we use or derive from SafeHandle?
public Function(Lib library, string functionName)
{
_funcPtr = library.GetProcAddress(functionName);
if(_funcPtr == IntPtr.Zero)
{
GC.SuppressFinalize(this);
throw new Exception("Error binding function.");
}
}
private void Release()
{
if(_funcPtr != IntPtr.Zero)
NativeMethods.HypotheticalForgetProcAddressMethod(_funcPtr);
_funcPtr = IntPtr.Zero; // avoid double free.
}
public void Dispose()
{
Release();
GC.SuppressFinalize(this);
}
~Function()
{
Release();
}
}
然后MyClass 将是:
public sealed class MyClass : IDisposable
{
private Library _lib;
private Function _func;
public MyClass(string dllPath)
{
_lib = new Library(dllPath); // If this fails, we throw here, and we don't need clean-up.
try
{
_func = new Function(_lib, "MyFunction");
try
{
SomeMethodThatCanThrowJustToComplicateThings();
}
catch
{
_func.Dispose();
throw;
}
}
catch
{
_lib.Dispose();
throw;
}
}
public void Dispose()
{
_func.Dispose();
_lib.Dispose();
}
}
这使得构造函数有点冗长,我宁愿避免两件可能出错的事情首先影响两件需要清理的事情。它确实反映了为什么我喜欢对不同字段进行明确的清理;我可能想清理两个字段,或者只清理一个字段,具体取决于异常发生的位置。