C# 如何更好地实现.NET IDisposable类?
如果这个问题有点过于开放,请原谅我,但我在这里看到过类似的语言讨论帖子,所以我想我会冒险一试 无论如何,我已经阅读了一些MSDN帮助页面和各种其他博客,主题是正确实现C# 如何更好地实现.NET IDisposable类?,c#,.net,idisposable,C#,.net,Idisposable,如果这个问题有点过于开放,请原谅我,但我在这里看到过类似的语言讨论帖子,所以我想我会冒险一试 无论如何,我已经阅读了一些MSDN帮助页面和各种其他博客,主题是正确实现IDisposable类。我觉得我对事物理解得很好,但我不得不怀疑在建议的类结构中是否存在缺陷: public class DisposableBase : IDisposable { private bool mDisposed; ~DisposableBase() { Dispose(f
IDisposable
类。我觉得我对事物理解得很好,但我不得不怀疑在建议的类结构中是否存在缺陷:
public class DisposableBase : IDisposable
{
private bool mDisposed;
~DisposableBase()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!mDisposed)
{
if (disposing)
{
// Dispose managed resources
mManagedObject.Dispose();
}
// Dispose unmanaged resources
CloseHandle(mUnmanagedHandle);
mUnmanagedHandle = IntPtr.Zero;
mDisposed = true;
}
}
}
任何时候,只要将上述内容用作基类,就需要依赖子类的实现者在必要时正确重写Dispose(bool)方法。简言之,派生类必须确保从其重写版本中调用基Dispose(bool)方法。否则,基类的非托管资源可能永远不会被释放,从而破坏IDisposable接口的主要用途
我们都知道虚拟方法的好处,但在这种情况下,它们的设计似乎有缺陷。事实上,我认为当试图设计可视化组件和类似的基类/派生类结构时,虚拟方法的这一特殊缺点经常表现出来
考虑以下更改,使用受保护的事件而不是受保护的虚拟方法:
public class DisposeEventArgs : EventArgs
{
public bool Disposing { get; protected set; }
public DisposeEventArgs(bool disposing)
{
Disposing = disposing;
}
}
public class DisposableBase : IDisposable
{
private bool mDisposed;
protected event EventHandler<DisposeEventArgs> Disposing;
~DisposableBase()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// This method is now private rather than protected virtual
private void Dispose(bool disposing)
{
if (!mDisposed)
{
// Allow subclasses to react to disposing event
AtDisposing(new DisposeEventArgs(disposing));
if (disposing)
{
// Dispose managed resources
mManagedObject.Dispose();
}
// Dispose unmanaged resources
CloseHandle(mUnmanagedHandle);
mUnmanagedHandle = IntPtr.Zero;
mDisposed = true;
}
}
private void AtDisposing(DisposeEventArgs args)
{
try
{
EventHandler<DisposeEventArgs> handler = Disposing;
if (handler != null) handler(this, args);
}
catch
{
}
}
}
公共类DisposeEventArgs:EventArgs
{
公共布尔处理{get;protected set;}
公共处置事件(bool处置)
{
处置=处置;
}
}
公共类DisposableBase:IDisposable
{
私人住宅;
受保护的事件处理程序;
~DisposableBase()
{
处置(虚假);
}
公共空间处置()
{
处置(真实);
总干事(本);
}
//此方法现在是私有的,而不是受保护的虚拟方法
私有无效处置(bool处置)
{
如果(!mDisposed)
{
//允许子类对事件作出反应
AtDisposing(新DispositeEventargs(disposing));
如果(处置)
{
//处置托管资源
mManagedObject.Dispose();
}
//处置非托管资源
闭合手柄(mUnmanagedHandle);
mUnmanagedHandle=IntPtr.Zero;
mDisposed=true;
}
}
处置时的私有无效(处置目标args)
{
尝试
{
EventHandler=Disposing;
if(handler!=null)handler(this,args);
}
抓住
{
}
}
}
在这种设计中,无论子类是否订阅Disposing事件,都将始终调用基类的Dispose(bool)方法。我在这个修改后的设置中看到的最大缺陷是,调用事件侦听器的时间没有预先确定的顺序。如果存在多个继承级别,这可能会有问题,例如子类的侦听器可能在其子类B的侦听器之前触发。这个缺陷严重到使我修改的设计无效吗
这种设计困境使我希望有一些类似于
virtual
的方法的修饰符,但这将确保始终调用基类的方法,即使子类重写了该函数。如果有更好的方法来实现这一点,我将非常感谢您的建议。您在这里使用的是事件
,而实际上您需要使用继承机制,如虚拟
。对于这样的场景,我希望确保始终调用我的实现,但希望允许基类定制,我使用以下模式
private void Dispose(bool disposing)
if (mDisposed) {
return;
}
if (disposing) {
mManagedObject.Dispose();
}
// Dispose unmanaged resources
CloseHandle(mUnmanagedHandle);
mUnmanagedHandle = IntPtr.Zero;
mDisposed = true;
DisposeCore(disposing);
}
protected virtual void DisposeCore(bool disposing) {
// Do nothing by default
}
通过这种模式,我确保了基类
Dispose
实现将始终被调用。派生类不能简单地忘记调用基方法来阻止我。他们仍然可以通过覆盖DisposeCore
选择dispose模式,但不能破坏基类契约 派生类可以简单地重新实现IDisposable
,从而防止调用dispose方法,因此您也无法确保这一点
就我个人而言,我不会使用这两种模式。我更喜欢基于
SafeHandle
和类似的机制,而不是自己实现终结器。考虑清楚地表明Dispose没有被调用,以便有人能够捕获它。当然,只有在使用定义的调试编译器指令编译代码时,才会调用Debug.WriteLine
public class DisposableBase : IDisposable
{
private bool mDisposed;
~DisposableBase()
{
if (!mDisposed)
System.Diagnostics.Debug.WriteLine ("Object not disposed: " + this + "(" + GetHashCode() + ")";
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
无论是否有子类重写Dispose()(可以是通过override或new),析构函数都将被调用,但您的析构函数将被调用(~DisposableBase()),因此我打赌,将您的清理逻辑放在这里可能是一个很好的起点 这是一篇关于析构函数的有趣文章:您可以将其分解:
- 只有非托管资源才需要析构函数(终结器)李>
- 使用Safehandle可以将未管理的资源转换为托管资源李>
- 因此,你不需要一个析构函数。这是处置模式的一半李>
虚拟void Dispose(bool)
来解决基本/派生类问题。这就给派生类带来了调用base.Dispose(disposing)
的负担,这是问题的核心。我使用两种方法:
1) 阻止它。有了一个密封的基类,你就不用担心了
sealed class Foo:IDisposable
{
void Dispose() { _member.Dispose(); }
}
2) 检查一下。像@j-agent的答案,但有条件。当性能可能成为问题时,您不希望在生产代码中使用终结器:
class Foo:IDisposable
{
void Dispose() { Dispose(true); }
[Conditional("TEST")] // or "DEBUG"
~Foo { throw new InvalidOperation("somebody forgot to Dispose") }
}
它可以工作,但是否值得偏离参考实现?@HenkHolterman-IMHO-yes。如果参考样本不能充分满足我的约束,我会根据需要偏离。我喜欢这个想法,而且它的代码肯定比使用事件少。但是,如果有一个以上的继承级别,那么这个继承级别是否仍然会受到相同的原始问题的影响(即孙辈不支持继承)