如何更好地实现.NET IDisposable类?

时间:2011-10-26 17:07:07

标签: c# .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
        {
        }
    }
}

使用此设计,无论子类是否订阅Disposing事件,都将始终调用基类'Dispose(bool)方法。我可以通过这个修改过的设置看到的最大缺陷是,在调用事件监听器时没有预定的顺序。如果存在多个级别的继承,例如,这可能是有问题的。 SubclassA的侦听器可能在其子SubclassB的侦听器之前被触发。这个缺陷是否足以使我的修改设计无效?

这种设计困境让我希望有一些类似于virtual的方法的修饰符,但它确保始终调用基类的方法,即使子类覆盖了该函数。如果有更好的方法来实现这一点,我将非常感谢您的建议。

5 个答案:

答案 0 :(得分:5)

当你真的要使用像event这样的继承机制时,你在这里使用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来选择处置模式,但是他们不能破坏基类合同。

答案 1 :(得分:2)

派生类可以简单地重新实现IDisposable,从而防止调用dispose方法,因此您无法确保这两种方法。

我个人不会使用任何一种模式。我更喜欢在SafeHandle和类似的机制上构建,而不是自己实现终结器。

答案 2 :(得分:2)

考虑明确表示Dispose没有被调用,所以有人会抓住它。当然,只有在使用DEBUG编译器指令定义编译代码时才会调用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);
  }

答案 3 :(得分:1)

你可以分解它:

  • 只有非托管资源才需要析构函数(终结器)。
  • 使用Safehandle可以将未管理的资源转换为托管资源。
  • Ergo:你不需要析构函数。这使Dispose模式减半。

参考设计使用virtual void Dispose(bool)来满足Base / Derived类问题。这会使派生类的负担调用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") } 
}

答案 4 :(得分:0)

无论是否有任何子类重写Dispose()(可以通过override或new),析构函数都将被调用,但是你的析构函数将被调用(~DisposableBase())所以我打赌你的逻辑用于清理可以是一个很好的起点。

这是一篇关于析构函数的文章:http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx