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

发布于 2024-12-12 15:24:15 字数 2728 浏览 1 评论 0原文

如果这个问题有点过于开放式,请提前原谅我,但我在这里看到了类似的语言讨论帖子,所以我想我应该冒险一试。

不管怎样,我已经阅读了一些关于正确实现 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

聆听风音 2024-12-19 15:24:15

当您确实想要使用像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 来选择处置模式,但他们不能破坏基类契约。

You're using an event here when really you want to use an inheritance mechanism like virtual. 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 pattern

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
}

With 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 overriding DisposeCore but they can't break the base class contract.

放手` 2024-12-19 15:24:15

派生类可以简单地重新实现 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.

护你周全 2024-12-19 15:24:15

考虑表明 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);
  }

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.

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);
  }
沉默的熊 2024-12-19 15:24:15

您可以将其分解:

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

该参考设计使用virtual void Dispose(bool)来解决基类/派生类问题。这给派生类带来了调用 base.Dispose(disusing) 的负担,这是您问题的核心。我使用两种方法:

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") } 
}

You can break it down:

  • A destructor (finalizer) is only needed for unmanaged resources.
  • Using a Safehandle can turn an unmanged resource into a managed resource.
  • Ergo: You won't need a destructor. That halves the Dispose pattern.

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 call base.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.

sealed class Foo:IDisposable 
{ 
   void Dispose() { _member.Dispose(); } 
}

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:

class Foo:IDisposable 
{ 
  void Dispose() { Dispose(true); }

  [Conditional("TEST")]  // or "DEBUG"
  ~Foo { throw new InvalidOperation("somebody forgot to Dispose") } 
}
恋竹姑娘 2024-12-19 15:24:15

无论任何子类是否重写 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文