当父类也实现IDisposable时,在子类上实现IDisposable

38

我有一个父类和一个子类,它们都需要实现 IDisposable 接口。那么在哪里应该使用 virtual 关键字(以及 base.Dispose())呢?当我仅重写 Dispose(bool disposing) 方法时,感觉很奇怪,因为我声明了自己实现了 IDisposable 接口,但没有明确的 Dispose() 函数(只是利用了继承下来的函数),而其他方法则完全实现了。

我的做法(大大简化):

internal class FooBase : IDisposable
{
    Socket baseSocket;

    private void SendNormalShutdown() { }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed = false;
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                SendNormalShutdown();
            }
            baseSocket.Close();
        }
    }

    ~FooBase()
    {
        Dispose(false);
    }
}

internal class Foo : FooBase, IDisposable
{
    Socket extraSocket;

    private bool _disposed = false;
    protected override void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            extraSocket.Close();
        }
        base.Dispose(disposing);
    }

    ~Foo()
    {
        Dispose(false);
    }

}

1
Msdn提供了有关如何对实现IDisposable的对象进行子类化的建议。 - PeterM
5个回答

33
当我只覆盖Dispose(bool disposing)调用时,感觉很奇怪声明我实现IDisposable但没有显式的Dispose()函数(只是利用继承的函数),但其他一切都有。
这不是你应该关心的事情。
当你子类化一个IDisposable类时,所有的“Dispose模式”管道已经被基类处理了。你真的什么都不应该做,除了重写protected Dispose(bool)方法,并跟踪是否已经被处理(以正确地引发ObjectDisposedException)。
详情请参见我的博客文章:从一个IDisposable类进行子类化

通常情况下,考虑将IDisposable类封装而不是继承它是一个好主意。虽然在某些情况下继承IDisposable类是适当的,但这种情况相对较少。封装通常是更好的选择。


啊,我猜我担心是多余的,我离正确的方法很近了(不知道我从哪里得到重新实现终结器的想法...嗯)。而且,总的来说,我发现自己越来越少使用继承。 - Tanzelax
Reed,你的博客太棒了...但是请注意这里的答案数量-7(其中1个是我的 :))....人们无法充分欣赏这一点..可惜。 - Boppity Bop

6

我总是倾向于参考乔·达菲(Joe Duffy)对这种模式的深入研究。对我来说,他的版本是至高无上的。

http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/

首先需要记住的是,大多数情况下不需要使用终结器。它用于清理直接持有本机资源的未托管资源,即仅限于没有自己的终结器的资源。
下面是一个基类和子类对的示例。
// Base class

    #region IDisposable Members

    private bool _isDisposed;

    public void Dispose()
    {
        this.Dispose(true);
        // GC.SuppressFinalize(this); // Call after Dispose; only use if there is a finalizer.
    }

    protected virtual void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // Clear down managed resources.

                if (this.Database != null)
                    this.Database.Dispose();
            }

            _isDisposed = true;
        }
    }

    #endregion


// Subclass

    #region IDisposable Members

    private bool _isDisposed;

    protected override void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // Clear down managed resources.

                if (this.Resource != null)
                    this.Resource.Dispose();
            }

            _isDisposed = true;
        }

        base.Dispose(isDisposing);
    }

    #endregion

请注意,子类有自己的_isDisposed成员。还要注意对资源进行空值检查,因为您不希望在这些块中出现任何异常。
卢克

5

在不需要复杂化的情况下,为什么要把事情搞得那么麻烦呢?

由于您没有封装任何非托管资源,因此您不需要进行所有这些与终结有关的操作。而且,您的类是内部的,这表明您可以在自己的程序集中控制继承层次结构。

因此,直接的方法应该是:

internal class FooBase : IDisposable 
{ 
  Socket baseSocket; 

  private void SendNormalShutdown() 
  { 
    // ...
  } 

  private bool _disposed = false; 

  public virtual void Dispose() 
  { 
    if (!_disposed)
    { 
      SendNormalShutdown(); 
      baseSocket.Close(); 
      _disposed = true;
    } 
  } 
} 

internal class Foo : FooBase
{ 
  Socket extraSocket; 

  private bool _disposed = false; 

  public override void Dispose()
  { 
    if (!_disposed)
    { 
      extraSocket.Close(); 
      _disposed = true;
    } 

    base.Dispose(); 
  } 
} 

即使您有未经管理的资源,我认为将它们封装在自己的可处理类中,并像使用任何其他可处理资源一样使用它们,比如上面的代码一样,会更好。请访问此链接了解更多信息。

1
除了继承自Object的微不足道的情况之外,您能想到任何子类应该具有清理finalizer而父类没有的情况吗?我可以理解添加一个finalizer,其工作是抱怨未正确释放的对象,但在我的看法中,任何需要清理的内容都应该在其自己的可终结类中;我想不出任何例外。 - supercat
@supercat:我完全同意。 - Jordão
你应该防止覆盖公共Dispose方法,而是覆盖受保护的方法。请参见:https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose#implementing-the-dispose-pattern-for-a-derived-class - David Anderson
嘿,@DavidAnderson,我故意没有遵循Microsoft的可处理对象模式,因此没有受保护的方法。请看看我的做法这里这里 - Jordão

4

这种模式的思想是,您覆盖虚拟的Dispose方法,如有必要,请调用base.Dispose。基类会处理其余部分,调用虚拟Dispose方法(因此正确实现)。子类不应需要实现IDisposable(通过继承它已经是IDisposable)。


1
我想我可以删除显式的 : IDisposable,因为它已经继承了该接口。但是我认为明确声明“这个类有一些特殊的处理”还是不错的。谢谢你的建议。 :) - Tanzelax

2

1
我不确定我喜欢为已经臃肿的IDisposable模式添加更多虚拟/抽象方法的想法... - Tanzelax
@Tanzelax 每个人有自己的想法,我认为这段代码对我很有用,当我需要处理一组需要可处理的类的时候。大部分设置在最高级别的类中完成,并且每个子类只需要少量额外的代码。 - Grant Palin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接