【问题标题】:Take on IDisposable pattern采用 IDisposable 模式
【发布时间】:2013-10-18 12:46:27
【问题描述】:

好的,我已经阅读了一些关于 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;
        }
    }

}

【问题讨论】:

  • 处置模式要求您将 Dispose(bool) 方法保护为虚拟的。这样派生类就可以覆盖它并调用基方法。实际上,使用处置模式在 99.9% 的情况下都是错误的,编写析构函数几乎从来都不是正确的做法。框架类有一个,你应该把它留给他们。喜欢 SafeHandle。
  • 你并没有改进标准模式,只是让事情变得混乱。例如,您的 SuppressFinalize 位于错误的位置。
  • DRY IDisposable Pattern 的可能重复项
  • 一篇对理解 IDisposeable 对象很有帮助的文章(以及为什么“标准模式”实际上不是一个好的模式)阅读Stephen Cleary 撰写的文章“IDisposable: What Your Mother Never Told You About Resource Deallocation”。

标签: c# .net memory-management garbage-collection


【解决方案1】:

我不希望每种类型都有终结器。很少需要在终结器中执行任何工作。如果 disposing 是真的,几乎所有的实现都不会做任何事情。终结器会影响性能,因为它们会导致升级到 Gen2,需要清理两个集合,并且调用终结器是单线程的。

大多数类不包装非托管资源,如果这样做,它们应该使用SafeHandle 类型或其他类型。这也使得终结器变得不必要。

【讨论】:

  • 非常有帮助,我不明白对性能的影响。这就是为什么我认为我会超级安全并确保总是在那里调用 Dispose。干杯。
【解决方案2】:

我没有看到您的代码有任何真正的改进,但由于该模式是非标准的,其他开发人员可能更难理解它。要使用您的模式创建派生类,您需要以下内容:

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 参数将始终为真。您的 Dispose 方法将是:

protected override void Dispose(bool disposing)
{
    _child1.Dispose();
    _child2.Dispose();
}

一般而言,避免使用终结器对性能有好处,这就是为什么您调用 GC.SuppressFinalize 来消除您通过实现终结器所造成的伤害。

但在许多情况下,您仍然需要跟踪对象的已处置状态,因为如果在对象已处置后调用方法,则必须抛出 ObjectDisposedException,在这种情况下,我的简化 Disposed 方法是太简单。下面是一个示例,说明如何在不复制每个子类中的 _disposed 标志并仍然使用标准处置模式的情况下进行处理:

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

【讨论】:

    【解决方案3】:

    你说:

    我的问题与从 IDisposable 基类继承有关。全部 我看到的示例一遍又一遍地编写相同的代码块 子类,我没有看到优势。

    这不是真的,在 IDisposable 模式中的方法:

    protected virtual void Dispose(bool disposing)
    

    应该被继承类覆盖。

    您需要注意 Dispose(bool disposing) 方法实际上是您的 LocalDisposing(bool disposing) 方法。而这个事实,我认为,是你困惑的根源。

    请阅读这本开创性书籍的相关部分: Framework Design Guidelines, Second edition

    引用本书:

    如果你从一个已经实现了 模式,只需重写 Dispose(bool) 方法即可提供额外的 资源清理逻辑

    在派生类中,代码如下所示:

    protected override void Dispose(bool disposing)
    {
      if(disposing)
      {
          //release own resources
      } 
    }
    

    还要注意,在这种情况下,您应该只在非虚拟 Dispose 方法中调用 GC.SuppressFinalize(this)。 同样在您的代码中,您正在隐式实现 IDisposable 接口,请务必注意这一点。

    【讨论】:

    • 如果我采取在子类中覆盖protected virtual void Dispose(bool disposing)... 的策略,那么我将再次声明、设置和检查private bool _isDisposed 跟踪变量。这已经在超类中完成了。这几乎就是我开车的目的。为什么要重新复制支持字段和条件逻辑?基类已经知道这一切,那么为什么不加入一个在正确时间调用的方法呢?
    • 我不知道为什么要复制支持字段,Dispose 会收到一个参数“disposing”,您应该在释放资源时使用它来控制。关于条件逻辑它只是'如果(处置)'。
    • 这是一个好点 - 我的错。只是几乎每个答案(咳咳,见马丁的)都这样做,所以它已经成为大多数人似乎认为的标准实现的一部分,但它似乎是多余的。如果基类跟踪您是否已被处置,为什么还要再做一次?感谢您和我在一起,我会接受标准模式。
    • 欢迎您,我还编辑了答案以更具说明性。我推荐我在答案中引用的书,因为它能让你成为更好的程序员。
    猜你喜欢
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    相关资源
    最近更新 更多