Должен ли я реализовать IDisposable, если у класса есть IDisposable, но нет неуправляемых ресурсов?

Документация MSDN и множество ответов здесь, в StackOverflow, относятся к длине, чтобы правильно выполнить реализацию IDisposable, например. MSDN IDisposable, MSDN Внедрение IDisposable, qaru.site/info/1025/...

Однако ни один из них, похоже, не охватывает более распространенный случай использования: что делать, когда мой класс имеет член 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 недолговечен, поэтому говорит вызов Dispose например используя using:

Использовать IDisposable только в том случае, если вы используете неуправляемые ресурсы непосредственно. Если ваше приложение просто использует объект, который реализует IDisposable, не предоставляют реализацию IDisposable. Вместо этого вы должен вызвать объект IDisposable. Внедрить реализацию, когда вы закончены с его использованием.

конечно, это невозможно здесь, где нам нужен экземпляр, чтобы выжить дольше, чем вызов метода!?

Я подозреваю, что правильным способом сделать это было бы реализовать 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, если ваш класс не имеет неуправляемых ресурсов, заставляет меня не решаться на это... Что плохой программист делать?

Ответ 1

Похоже, что ваше дело действительно покрывается некоторой документацией, а именно предупреждением о дизайне CA1001: Типы, которые располагают одноразовыми полями, должны быть одноразовыми.

В этой ссылке есть пример того, как должна выглядеть ваша реализация 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);
    }
  }

Ответ 2

Как вы собрали, вам нужно сделать 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);
    }
}

См. также