当一个类具有IDisposable成员但没有非托管资源时,我是否应该实现IDisposable接口?

30

MSDN文档和StackOverflow上的许多答案都详细讨论了正确实现 IDisposable 的方法,例如:MSDN IDisposableMSDN Implementing IDisposable一篇优秀的StackOverflow问答

然而,它们似乎都没有涵盖我更常见的用例:当我的类有一个比一个方法生命周期更长的 IDisposable 成员时该怎么办?例如:

  class FantasticFileService
  {
    private FileSystemWatcher fileWatch; // FileSystemWatcher is IDisposable

    public FantasticFileService(string path)
    {
      fileWatch = new FileSystemWatcher(path);
      fileWatch.Changed += OnFileChanged;
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
      // blah blah
    }
  }

最接近解决此问题的MSDN仅覆盖了IDisposable实例短暂存在的情况,因此建议使用using调用Dispose方法:

仅在直接使用未托管资源时才实现IDisposable。如果您的应用程序仅使用实现IDisposable的对象,则不要提供IDisposable实现。相反,当您使用完该对象时,应调用其IDisposable.Dispose实现。

当需要实例的寿命长于方法调用时,这显然是不可能的!?

我怀疑正确的做法是实现IDisposable(将责任传递给我的类的创建者来处理),但不需要所有终结器和protected virtual void Dispose(bool disposing)逻辑,因为我没有任何未管理的资源,即:

  class FantasticFileService : IDisposable
  {
    private FileSystemWatcher fileWatch; // FileSystemWatcher is IDisposable

    public FantasticFileService(string watch)
    {
      fileWatch = new FileSystemWatcher(watch);
      fileWatch.Changed += OnFileChanged;
    }

    public void Dispose()
    {
      fileWatch.Dispose();
    }
  }

但为什么官方文档中没有明确涵盖这种用例呢?并且它明确说明如果您的类没有非托管资源,则不要实现 IDisposable,这让我犹豫不决...一个可怜的程序员该怎么办呢?


你的第二个链接实现Dispose方法解释了他们所说的“在此处释放任何其他托管对象”。另外,我不确定你是否正确地实现了Dispose()。你是不是忘记了GC.SuppressFinalize(this); - user585968
我也不确定你是否正确实现了Dispose()方法,很可能是这样!问题在于如何在特定情况下正确地实现Dispose()方法,如果我知道的话就不会问了! - markmnl
无法找到关于“是的,在这种情况下应该实现IDisposable”的好链接,但我没有看到任何其他通常认同的方法来指定您的对象需要在结束时进行显式清理。请注意,除非您的类为sealed(https://dev59.com/zW865IYBdhLWcg3wTcvg),否则仍需要使用完整的Dispose模式。 - Alexei Levenkov
2个回答

25

看起来您的情况确实有一些文档可以参考,特别是设计警告 CA1001: 拥有可处置字段的类型应实现 IDisposable

那个链接中有一个示例,展示了您的 IDisposable 实现应该是什么样子。最终的设计指南可以在CA1063: 正确实现 IDisposable中找到。

  class FantasticFileService : IDisposable
  {
    private FileSystemWatcher fileWatch; // FileSystemWatcher is IDisposable

    public FantasticFileService(string watch)
    {
      fileWatch = new FileSystemWatcher(watch);
      fileWatch.Changed += OnFileChanged;
    }

    ~FantasticFileService()
    {
      Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing && fileWatch != null)
      {
        fileWatch.Dispose();
        fileWatch = null;
      }
    }

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

此外,看起来 https://dev59.com/S5Dea4cB1Zd3GeqPfr3x 上有一个类似的问题/答案。 - gnalck
谢谢,我喜欢官方链接,并且它明确地讨论了我的情况。看起来很相似,但是我只是碰巧使用FileSystemWatcher作为一个例子,它可以是任何IDisposable对象。 - markmnl
1
虽然为了后代的缘故,值得注意的是添加析构函数并不会改变代码的功能。还可以查看此链接下的评论:https://dev59.com/w3RB5IYBdhLWcg3wa2q2#628814 - gnalck
1
实际上,链接的页面说:“如果类没有直接拥有任何非托管资源,则不应该实现终结器。” - markmnl
检查/设置为 null 对于非可空类型是有问题的,您还需要使用一个 disposed 标志。 - kofifus
显示剩余3条评论

4

如您所知,您需要将 FantasticFileService:IDisposable 也设计为可释放的。 Dispose() 可以用于处理托管资源和非托管资源。

可以尝试以下代码:

class FantasticFileService:IDisposable
{
    private FileSystemWatcher fileWatch; // FileSystemWatcher is IDisposable
    private bool disposed;

    public FantasticFileService(string path)
    {
        fileWatch = new FileSystemWatcher(path);
        fileWatch.Changed += OnFileChanged;
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        // blah blah
    }

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
            if (fileWatch != null)
            {
                fileWatch.Dispose();
                fileWatch = null;                   
            }
            // Free any other managed objects here.
            //
        }

        // Free any unmanaged objects here.
        //
        disposed = true;
    }

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

参见


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