采用可识别的模式

本文关键字:模式 识别 | 更新日期: 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 接口,了解这一点很重要。