使用一次性对象的类的层次结构。在所有这些上实现IDisposable?

时间:2011-11-18 15:47:14

标签: c# idisposable

我有一个使用文件流的类。当app关闭时,它需要关闭流,所以我让类实现IDisposable。

该类是另一个类的成员,它是另一个类的成员等。一直到我的主应用程序。

因此,我是否必须在这些类的所有上实现IDisposable?

如果我将来更改文件实现以便在每次写入后关闭文件怎么办?我现在有一整套无意中实现IDisposable的类。

我想我对将IDisposable语义扼杀到除了链上的一些轻微的实现细节之外的不需要它们的类中感到不舒服。有没有办法解决这个问题?

6 个答案:

答案 0 :(得分:4)

通常,如果您的类型包含实现IDisposable的成员,则该类型也应该实现IDiposable。这是强制执行IDisposable模式的最简单方法。

我使用的一个例外是,如果我的类型契约包含一个方法,其中1)必须被调用,2)表示IDisposable资源的使用结束。在这种情况下,我觉得不能实现IDisposable而是使用该方法来调用Dispose

答案 1 :(得分:2)

如果你明确想要处理文件流,那么是的,你需要在任何持有IDisposable引用的类上实现IDisposable。如果在每次写入后处理文件流是合理的,即不会因频繁的问题而损害性能,那听起来更合适。

答案 2 :(得分: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
    }
}

虽然我不确定自己是否会使用此模式,但这样您就不必将“IDisposable”添加到除最初创建Filestream对象的类之外的任何类中。

答案 3 :(得分: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()被认为甚至模糊似乎合理的情况。

答案 4 :(得分:1)

在任何给定时刻,实现IDisposable的每个类型的每个实例都应该至少有一个(通常只有一个)实体,可以在不再需要它之后的某个时间,以及在它完全之前调用Dispose并最终放弃了。如果你的类型有一个IDisposable类型的字段,但是其他东西可能会被处理掉它可能引用的任何IDisposable实例,那么你不应该自己调用该字段上的Dispose。如果你的类型有一个IDisposable字段,那么一旦你完成它就没有其他人会使用那个对象,并且没有其他人需要Dispose它,那么你应该在对象上调用Dispose一旦你不再需要它。在许多情况下,您的对象将需要另一个对象,直到没有其他对象需要您的对象,并且您的对象将找到它的方式是当其他人在其上调用Dispose时(此时它将在其他对象上调用Dispose)。

有时候有用的一种模式是让一个类暴露一个Disposed事件,只要调用Dispose就会引发它。这可能很有用,例如,如果另一个对象为您的对象提供IDisposable的引用,它将需要一段时间,然后给您IDisposable的对象完成它。当对象仍然需要它时,它不能处理对象,并且你的对象不会处理它(因为你的对象不知道提供IDisposable的对象是否完成了它)。但是,如果为您的类提供了IDisposable挂钩到您的对象的Disposed处理程序的类,那么事件处理程序可以注意到您的对象不再需要IDisposable并立即处置它(如果您的对象是最后一个需要它)或者设置一个标志,这样当其他用户完成对象时,它将获得Disposed)。

如果你的对象在其整个生命周期中将保留一定数量的一次性对象,那么另一种模式可能很有用:保留一个IDisposable对象列表,然后让你的Dispose方法遍历列表并在其中处理。列表中的每个项目都应在其自己的Try / Catch块中处置;如果发生异常,则抛出一个CleanupFailureException(一个自定义类型),它具有InnerException等第一个或最后一个此类异常,并且还包含作为自定义属性发生的所有异常的列表。

答案 5 :(得分:1)

根据经验,当一个类型在实例字段中保留对IDisposable的引用时,我也将其设置为一次性的。但我通常会尽量避免在这种情况下找到自己;在可能的情况下,我尝试使用using块以相同的方法处理一次性用品。