我有一个类消耗一些非托管资源,我想确定性地释放它们并请求终结器不被调用为手头的对象.我Dispose()的班级方法实现了这一点.
如果在构造函数中抛出异常或出现其他错误或意外行为,我想Dispose()在抛出之前调用.但是,我很少遇到捕获抛出异常或在一次性对象的构造函数中处理错误然后调用Dispose()对象的实现 - 在很多情况下,作者将清理留给终结器.我没有读过任何声明调用Dispose()失败的构造函数是不好的做法的东西,但是在查看.NET源代码时,我还没有在一次性对象构造函数中遇到这样的异常或错误处理.
我可以Dispose()在"失败"的构造函数内部调用,仍然被认为是一个很好的编码公民吗?
编辑澄清 - 我在谈论构造函数内部:
public class MyClass : IDisposable
{
private IntPtr _libPtr = IntPtr.Zero;
public MyClass(string dllPath)
{
_libPtr = NativeMethods.LoadLibrary(dllPath);
if (_libPtr != IntPtr.Zero)
{
IntPtr fxnPtr = NativeMethods.GetProcAddress(_libPtr, "MyFunction");
if (fxnPtr == IntPtr.Zero)
{
Dispose(); // Cleanup resources - NativeMethods.FreeLibrary(_libPtr);
throw new NullReferenceException("Error linking library.");
}
}
else
{
throw new DllNotFoundException("Something helpful");
}
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
我不会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;
}
}
Run Code Online (Sandbox Code Playgroud)
到目前为止,既美好又简单。该对象要么已成功构造并且可以由调用它的代码释放,要么不需要清理。我们甚至可以阻止无操作终结,只是为了友善。最主要的是,在最后一件可能出错的事情之后,没有任何需要清理的东西。
进而:
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.
}
Run Code Online (Sandbox Code Playgroud)
同样,我可以确保清理,但我不会调用this.Dispose();虽然this.Dispose()可以执行相同的技巧,但我主要更喜欢在设置它但未能执行的相同方法(此处的构造函数)中显式地显示我正在清理的字段它的所有工作。一方面,唯一可以存在部分构造对象的地方是在构造函数中,因此我唯一需要考虑部分构造对象的地方是在构造函数中;我已将其设为类中其他_lib不为空的不变量。
让我们想象一下,函数必须与库分开发布,只是为了有一个更复杂的示例。然后我也会换行_funcPtr以符合简化规则;一个类要么具有一个需要清理的非托管资源Dispose()和一个终结器,要么具有一个或多个IDisposable需要清理的字段Dispose,或者不需要处置,但绝不是上述情况的组合。
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();
}
}
Run Code Online (Sandbox Code Playgroud)
然后就是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();
}
}
Run Code Online (Sandbox Code Playgroud)
这使得构造函数变得更加冗长,我宁愿避免两件事可能出错,从而影响首先需要清理的两件事。但这确实反映了为什么我喜欢对不同领域进行明确的清理;我可能想清理两个字段,或者只清理一个字段,具体取决于异常发生的位置。