采用可识别的模式
本文关键字:模式 识别 | 更新日期: 2023-09-27 18:35:52
好的,我已经阅读了一些关于IDisposable的最佳实践,我基本上明白了(终于)。
我的问题与从 IDisposable 基类继承有关。我看到的所有示例都在子类中一遍又一遍地编写相同的代码块,但我看不到优势。
为什么不简单地将虚拟方法烘焙到基类中,在正确的时间从(私有实现的)IDisposable 例程中调用它,这样子类就不会那么混乱,但仍然有机会管理它们的资源?
我建议的基类:
public abstract class DreamDisposableBase : IDisposable
{
private bool _disposed = false;
protected virtual void LocalDispose(bool disposing)
{
}
~DreamDisposableBase()
{
// finalizer being called implies two things:
// 1. our dispose wasn't called (because we suppress it therein)
// 2. we don't need to worry about managed resources; they're also subject to finalization
// so....we need to call dispose with false, meaning dispose but only worry about *unmanaged* resources:
dispose(false);
}
void IDisposable.Dispose()
{
dispose(true); // true argument really just means that we're invoking it explicitly
}
private void dispose(bool disposing)
{
if (!_disposed)
{
// give sub-classes their chance to release their resources synchronously
LocalDispose(disposing);
if (disposing)
{
// true path is our cue to release our private heap variables...
}
// do stuff outside of the conditional path which *always* needs to be done - release unmanaged resources
// tell .net framework we're done, don't bother with our finalizer -
GC.SuppressFinalize(this);
// don't come back through here
_disposed = true;
}
}
}
我不希望每个类型都有一个终结器。很少需要在终结器中执行任何工作。如果释放为真,几乎所有的实现都不会做任何事情。终结器会损害性能,因为它们会导致升级到 Gen2,需要清理两个集合,并且调用终结器是单线程的。
大多数类不包装非托管资源,如果包装,则应使用 SafeHandle
类型或其他类型。这使得终结器也变得不必要。
我没有看到您的代码有任何真正的改进,但由于该模式是非标准的,因此其他开发人员可能更难理解它。要使用模式创建派生类,您需要如下所示的内容:
class DerivedDreamDisposable : DreamDisposableBase
{
protected override void LocalDispose(bool disposing)
{
if (disposing)
{
// Dispose aggregated objects that are disposable.
}
// Dispose unmanaged resources.
_disposed = true;
base.LocalDispose(disposing);
}
}
使用标准IDisposable
模式,派生类如下所示:
class DerivedDisposable : DisposableBase
{
bool _disposed;
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose aggregated objects that are disposable.
}
// Dispose unmanaged resources.
_disposed = true;
}
base.Dispose(disposing);
}
}
通过从DreamDisposable
派生,可以避免复制字段来跟踪对象的已释放状态。但是,除此之外,方法实际上是相同的。此外,在基类中LocalDispose
为空,并且已将代码移动到私有Dispose
方法中,但这可以通过执行小的重构来修复。
但是,许多类不会释放任何非托管资源,并且由于调用 Dispose
方法是幂等的(可以多次调用它),因此通常不必跟踪释放的状态,并且一次性代码简化为:
protected override void Dispose(bool disposing)
{
if (disposing)
{
_child1.Dispose();
_child2.Dispose();
}
}
如果继承层次结构中没有任何非托管资源,则不需要终结器,disposing
参数将始终为 true。然后,您的Dispose
方法将是:
protected override void Dispose(bool disposing)
{
_child1.Dispose();
_child2.Dispose();
}
通常,避免终结器有利于性能,这就是为什么您调用GC.SuppressFinalize
来消除通过实现终结器造成的伤害。
但是在许多情况下,您仍然需要跟踪对象的已释放状态,因为如果在释放对象后调用方法,则必须抛出ObjectDisposedException
,在这种情况下,我的简化Disposed
方法太简单了。下面是一个示例,说明如何在不复制每个子类中的 _disposed
标志的情况下处理这个问题,并且仍然使用标准 dispose 模式:
class DisposableBase : IDisposable
{
bool _disposed;
~DisposableBase()
{
Dispose(false);
GC.SuppressFinalize(this);
}
public void Dispose()
{
if (_disposed)
return;
Dispose(true);
_disposed = true;
}
public void DoStuff()
{
ThrowIfDisposed();
// Now, do stuff ...
}
protected virtual void Dispose(bool disposing)
{
// Dispose resources ...
}
protected void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(GetType().FullName);
}
}
任何派生类都不需要跟踪已释放的状态,而是应在依赖于未释放对象的所有公共方法中使用ThrowIfDisposed
。
你说:
我的问题与从 IDisposable 基类继承有关。都 我看到的示例在 子类,我没有看到优势。
这是不正确的,在 IDisposable 模式中,该方法:
protected virtual void Dispose(bool disposing)
应由继承类覆盖。
您需要注意,Dispose(bool disposing)
方法实际上是您的 LocalDisposing(bool disposing)
方法。我认为,这个事实就是你困惑的根源。
请阅读这本开创性书籍的相关部分:框架设计指南,第二版
引用这本书:
如果从已实现 模式,只需覆盖 Dispose(bool) 方法即可提供附加 资源清理逻辑
在派生类中,代码如下所示:
protected override void Dispose(bool disposing)
{
if(disposing)
{
//release own resources
}
}
另请注意,在这种情况下,应仅在非虚拟 Dispose 方法中调用GC.SuppressFinalize(this)
。同样在您的代码中,您正在隐式实现 IDisposable 接口,了解这一点很重要。