使用可释放对象的类层次结构。在所有类上实现IDisposable吗?

10

我有一个使用文件流的类。应用程序关闭时需要关闭流,因此我使该类实现IDisposable接口。

该类是另一个类的成员,该类又是另一个类的成员,以此类推,一直到我的主应用程序。

那么,我是否必须在所有这些类上实现IDisposable接口?

如果将来更改文件实现方式,使其在每次写入后关闭文件怎么办?现在我有一整套无需实现IDisposable接口的类。

我觉得将IDisposable的语义强行添加到没有任何需要的类中让我感到不舒服,而这只是某个下层实现细节的原因。有没有其他方法可以解决这个问题?


这不是一个类的层次结构,而是对象的嵌套。是的,拥有资源意味着需要IDisposable。最好的解决方案是避免持有(打开的)资源。 - H H
6个回答

4
一般来说,如果您的类型包含一个实现IDisposable接口的成员,则该类型也应该同时实现IDisposable接口。这是强制实施IDisposable模式的最简单方法。
我使用的唯一例外是,如果我的类型契约包含一个必须调用并且信号化IDisposable资源使用结束的方法。在这种情况下,我感觉可以不实现IDisposable接口,而是使用该方法来调用Dispose方法。

1
+1 不过,你能举个例子说明一个必须被调用而不能仅仅重构为 IDisposable 实现的方法吗? - Justin
@Justin,BCL 中的示例非常少。我主要使用这种技术来处理我的程序集中具有非常特定约定的“internal”类型。但是在 BCL 中,最接近的示例是 IAsyncResult。该约定指出,无论操作如何结束,都必须调用 EndInvoke。这是许多 IAsyncResult 实现释放它们提供的 WaitHandle 的地方。 - JaredPar
如果真的必须调用它,那么我会将其放在 Dispose() 和可能的 finalizer 中。事实上,有时我创建类纯粹是因为它们的构造函数和 Dispose() 给了我一个执行环绕模式。 - Jon Hanna

2

如果您明确想要释放文件流,则需要在任何持有对IDisposable的引用的类上实现IDisposable。如果每次写操作后释放文件流是合理的,即由于频繁的写入不会影响性能,那么这听起来更可取。


1
在任何时刻,每个实现IDisposable接口的类型的每个实例都应该至少有一个(通常恰好一个)可以期望在其不再需要之后但在完全废弃之前某个时刻调用Dispose的实体。如果您的类型具有IDisposable类型的字段,但可以预期其他内容处理任何IDisposable实例的引用,则不应自行调用该字段上的Dispose。如果您的类型具有IDisposable字段,并且一旦您完成使用该对象,就不再有人使用该对象或预期任何其他人来处理它,则应在不再需要它时调用该对象上的Dispose。在许多情况下,您的对象将需要其他对象,直到没有其他对象需要您的对象,您的对象将在其他人调用其Dispose时找出这一点(然后它将调用其他对象上的Dispose)。
有时候有一个模式是有一个类公开一个Disposed事件,每当Dispose被调用时就会引发该事件。这在某些情况下非常有用,例如,另一个对象给你的对象提供了一个IDisposable的引用,并且它需要一段时间,然后给你IDisposable的对象完成了。它不能在你的对象仍然需要它的时候处理该对象,而你的对象也不会处理它(因为你的对象不知道提供IDisposable的对象是否已经完成了它)。但是,如果提供IDisposable的类钩住了你的对象的Disposed处理程序,那么事件处理程序就可以注意到你的对象不再需要IDisposable,然后立即处理它(如果你的对象是最后一个需要它的对象),或者设置一个标志,以便当其他用户完成对象时它将被处理。
如果你的对象将在其生命周期内保留一定的可处理对象集,则另一种有用的模式是保持一个IDisposable对象列表,然后让你的Dispose方法遍历该列表并处理其中的内容。列表中的每个项目都应在其自己的Try/Catch块中处理;如果发生异常,请抛出一个CleanupFailureException(自定义类型),该异常具有第一个或最后一个异常作为其InnerException,并且还包括所有发生的异常列表作为自定义属性。

你能举一个第二段的例子吗?我很难理解事件是如何发生的;如果我的对象没有调用Dispose(),它如何通知IDisposable创建者它已经完成了。 - Ian Goldby
@IanGoldby:一个例子是一个显示 Image 对象的控件。如果图像纯粹是为了控件的好处而创建的,那么在控件被处理时释放该图像就是有意义的。然而,一张图片可能会为多个控件或甚至多个窗体提供帮助,而这些控件或窗体并不知道其他控件或窗体的存在。如果拥有该图像的对象对其存储的每个控件持有 WeakReference,并订阅来自这些控件的 Disposed 事件,它可以将 Disposed 事件用作触发器... - supercat

1
通常情况下,当一个类型在实例字段中保留对 IDisposable 的引用时,我也会使其可处理。但是我通常会尽量避免陷入这种情况;如果可能的话,我会在创建它们的同一方法中使用 using 块来处理可处理对象。

1

这取决于您如何实现使用文件流的类。如果该类创建了文件流,则应负责处理它的释放。但是,如果您将其更改为方法接受文件流作为参数,则不再拥有文件流,因此不必负责处理。

如果该类是某种层次结构的一部分,则可以从顶部开始添加文件流作为参数,并将其引入到所有实际使用它的方法中。

例如:

public class Class1
{
    private readonly Class2 SomeObject = new Class2();

    public void DoWork1(Filestream stream)
    {
        SomeObject.DoWork2(stream);
    }
}

public class Class2
{
    public void DoWork2(Filestream stream)
    {
        // Do the work required with the Filestream object
    }
}

虽然我不确定我会自己使用这种模式,但这将允许您除了最初创建Filestream对象的类之外,不必向任何类添加“IDisposable”。


1
你需要在每个实现中使用IDisposable,但这并不一定需要在每个代码中显式实现。让继承为你完成工作。
两种方法:
class FileHandlingClass : IDisposable
{
  private FileStream _stm;
  /* Stuff to make class interesting */
  public void Disposable()
  {
    _stm.Dispose();
  }
  /*Note that we don't need a finaliser btw*/
}

class TextHandlingClass : FileHandlingClass
{
  /* Stuff to make class interesting */
}

现在我们可以做到:

using(TextHandlingClass thc = new TextHandlingClass())
  thc.DoStuff();

等等。

这一切都能够正常工作,是因为TextHandlingClass继承了它所需要的唯一一个IDisposable实现。

如果我们需要进一步释放资源,情况就会变得更加复杂:

假设我们有一个处理池化XmlNameTable对象的类(为什么这是个好主意是另一个话题),并且将其释放后返回表到池中,而它被XmlHandlingClass使用。现在,我们可以通过以下方式来处理:

class XmlHandlingClass : FileHandlingClass, IDisposable
{
  PooledNameTable _table;
  /* yadda */
  public new void Dispose() // another possibility is explicit IDisposable.Dispose()
  {
    _table.Dispose();
    base.Dispose();
  }
}

现在,这在以下方面表现得很出色:

using(XmlHandlingClass x = new XmlHandlingClass())
  x.Blah();

但不包括:

using(FileHandlingClass x = new XmlHandlingClass())
  x.Blah()

在后一种情况下,只使用FileHandlingClass实现(幸运的是,不将池化名称表返回到池中并不重要,大多数Dispose()的情况更为关键)。因此,如果需要覆盖,我们应该这样做:
//Allow for Disposal override
class FileHandlingClass : IDisposable
{
  private FileStream _stm;
  /* Stuff to make class interesting */
  public virtual void Disposable()
  {
    _stm.Dispose();
  }
  /*Note that we don't need a finaliser btw*/
}

//Still don't care here
class TextHandlingClass : FileHandlingClass
{
  /* Stuff to make class interesting */
}

class XmlHandlingClass : FileHandlingClass
{
  PooledNameTable _table;
  /* yadda */
  public override void Dispose()
  {
    _table.Dispose();
    base.Dispose();
  }
}

现在我们在调用Dispose()时更加安全,但仍然只需要在必要的情况下自己实现它。

在第二种情况下会有一点性能损失,但真的很小。我现在指出这一点只是为了反对在需要覆盖Dispose()的情况下考虑第一种情况。


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