如何更好地实现.NET IDisposable类?
如果这个问题有点过于开放式,请提前原谅我,但我在这里看到了类似的语言讨论帖子,所以我想我应该冒险一试。
不管怎样,我已经阅读了一些关于正确实现 IDisposable 类主题的 MSDN 帮助页面和各种其他博客。我觉得我很好地理解了事情,但我不得不怀疑建议的类结构中是否存在缺陷:
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
{
}
}
}
通过这种设计,无论子类是否订阅 Dispose 事件,都将始终调用基类的 Dispose(bool) 方法。我发现这个修改后的设置的最大缺陷是调用事件侦听器时没有预定的顺序。如果存在多个继承级别,这可能会出现问题,例如,SubclassA 的侦听器可能会在其子 SubclassB 的侦听器之前被触发。这个缺陷是否严重到足以使我修改后的设计失效?
这种设计困境让我希望有某种类似于 virtual 的方法修饰符,但可以确保基类的方法始终被调用,即使子类覆盖了该函数。如果有更好的方法来实现这一目标,我将非常感谢您的建议。
Forgive me in advance if this question is a little too open-ended, but I've seen similar language discussion posts here so I figured I'd take the plunge.
Anyway, I have read several MSDN help pages and various other blogs on the subject of properly implementing IDisposable
classes. I feel like I understand things pretty well, but I have to wonder if there's a flaw in the suggested class structure:
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;
}
}
}
Anytime the above is supposed to serve as a base class, you rely on the implementer of the subclass to properly override the Dispose(bool) method where necessary. In short, derived classes must ensure they invoke the base Dispose(bool) method from within their overridden version. If not, the base class' unmanaged resources may never get freed, defeating the primary purpose of the IDisposable interface.
We all know the benefits of virtual methods, but it seems like in this case their design falls short. In fact, I think this particular shortcoming of virtual methods manifests itself frequently when trying to design visual components and similar base/derived class structures.
Consider the following change, using a protected event rather than a protected virtual method:
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
{
}
}
}
With this design, the base class' Dispose(bool) method will always be called, regardless of whether subclasses subscribe to the Disposing event or not. The biggest flaw that I can see with this revised setup is that there is no predetermined order for when event listeners are called. This could be problematic if there are multiple levels of inheritance, e.g. SubclassA's listener might be triggered before its child SubclassB's listener. Is this flaw serious enough to invalidate my revised design?
This design dilemma makes me wish there were some sort of modifier for methods that was similar to virtual
but which would ensure that the base class' method was always called, even if a subclass overrode that function. If there's a better way to achieve this, I would greatly appreciate your suggestions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
当您确实想要使用像
virtual
这样的继承机制时,您在这里使用了事件
。对于这样的场景,我想确保始终调用我的实现,但希望允许基类自定义,我使用以下模式使用此模式,我确保始终调用我的基类
Dispose
实现。派生类无法仅仅因为忘记调用基方法而阻止我。他们仍然可以通过重写 DisposeCore 来选择处置模式,但他们不能破坏基类契约。You're using an
event
here when really you want to use an inheritance mechanism likevirtual
. For scenarios like this where I want to ensure my implementation is always called but want to allow for base class customization I use the following patternWith this pattern I've ensured my base class
Dispose
implementation will always be called. Derived classes can't stop me by simply forgetting to call a base method. They can still opt into the dispose pattern by overridingDisposeCore
but they can't break the base class contract.派生类可以简单地重新实现 IDisposable ,从而阻止调用您的 dispose 方法,因此您也无法确保这一点。
就我个人而言,我不会使用这两种模式。我更喜欢构建 SafeHandle 和类似的机制,而不是自己实现终结器。
The derived class can simply re-implement
IDisposable
and thus prevent your dispose method from being called, so you can't ensure that either.Personally I wouldn't use either pattern. I prefer building on
SafeHandle
and similar mechanisms, instead of implementing finalizers myself.考虑表明 Dispose 没有被调用,以便有人能够捕获它。当然,仅当使用定义的 DEBUG 编译器指令编译代码时才会调用 Debug.WriteLine 。
Consider making it apparent that Dispose is not being called so someone will catch it. Of course
Debug.WriteLine
will only be called when the code is compiled with DEBUG compiler directive defined.您可以将其分解:
该参考设计使用
virtual void Dispose(bool)
来解决基类/派生类问题。这给派生类带来了调用base.Dispose(disusing)
的负担,这是您问题的核心。我使用两种方法:1)预防。有了密封的基类,您就不必担心。
2)检查一下。喜欢@j-agent 的答案,但有条件。当性能可能成为问题时,您不希望在生产代码中使用终结器:
You can break it down:
The reference design uses a
virtual void Dispose(bool)
to cater for the Base/Derived class problem. This puts the burden on the derived class to callbase.Dispose(disposing)
, the core of your question. I use 2 approaches:1) Prevent it. With a sealed base-class you won't have to worry.
2) Check it. Like @j-agent's answer but conditional. When performance could be an issue then you don't want the finalizers in Production code:
无论任何子类是否重写 Dispose() (可以通过 override 或 new),析构函数都会被调用,但是您的析构函数将被调用( ~DisposableBase() ),所以我打赌您的清理逻辑可能会出现良好的起点。
这是一篇关于析构函数的有趣文章: http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandDestructors.aspx
The destructor is going to be called no matter if any subclass overrides Dispose() (can be via override or new) but your destructor is going to be called ( ~DisposableBase() ) so i bet putting your logic for cleanup there can be a good starting point.
Here is an intersting article about destructors: http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx