【问题标题】:Why does a stream dispose when its writer is disposed?为什么当它的作者被释放时流会被释放?
【发布时间】:2011-05-20 11:49:06
【问题描述】:

考虑以下代码:

using (var ms = new MemoryStream())
{
    using(var writer = BinaryWriter(ms))
    {
        writer.Write(/*something*/);
        writer.Flush();
    }

    Assert.That(ms.Length > 0); // Throws ObjectDisposedException
}

一方面,一次性对象应该处理它的资源;我明白了,但另一方面,对象没有创建并且不拥有这个资源,它是提供的->调用代码应该对此负责......不是吗?

我想不出任何其他类似的情况,但是对于任何接收可丢弃对象的类自行处置它们是否是框架中的一致模式?

【问题讨论】:

    标签: c# .net-4.0 stream dispose


    【解决方案1】:

    我完全同意你的看法。这不是一致的行为,但它是如何实现的。关于这种行为的文档末尾有comments,这不是很直观。所有流编写者都只是获取底层流的所有权并处置它。就我个人而言,我总是像这样嵌套我的using 语句:

    using (var ms = new MemoryStream())
    using(var writer = BinaryWriter(ms))
    {
        writer.Write(/*something*/);
    }
    

    因此不应该编写像您放在 Assert 中的代码。

    【讨论】:

      【解决方案2】:

      有一个隐含的假设,即每个流只有一个写入器,因此写入器为方便起见假定流的所有权 - 然后您只需要清理一件事。

      但我同意;这并不总是正确的,而且往往不方便。某些实现(例如 DeflateStream、GZipStream)允许您进行选择。否则,唯一真正的选择是在编写器和底层流之间注入一个虚拟流; IIRC 在 Jon Skeet 的“MiscUtil”库中有一个 NonClosingStreamWrapper 正是这样做的:http://www.yoda.arachsys.com/csharp/miscutil/

      用法类似于:

      using (var ms = new MemoryStream())
      {
          using(var noClose = new NonClosingStreamWrapper(ms))
          using(var writer = BinaryWriter(noClose))
          {
              writer.Write(/*something*/);
              writer.Flush();
          }
      
          Assert.That(ms.Length > 0);
      }
      

      【讨论】:

        【解决方案3】:

        正确的做法是让流写入器的构造函数参数指示在构造函数存在时是否应该释放流。鉴于 Microsoft 没有这样做,最好定义一个 NonDisposingStream(Of T as Stream) 类来包装流但不将 Dispose 调用传递给包装的流。然后可以将一个新的 NonDisposingStream 传递给 StreamWriter 的构造函数,并且底层流将不会被处置(当然,有必要自己处置流)。

        拥有一个可以处理传入对象的对象很有用。虽然这种行为与对象创建者处理其处置的通常模式不一致,但在某些情况下,对象的创建者通常不知道该对象实际需要多长时间。例如,可能期望一个方法创建一个使用新 Stream 的新 StreamWriter。 StreamWriter 的所有者将知道何时应该释放它,但可能不知道内部流的存在。内部流的创建者将不知道外部 StreamWriter 将使用多长时间。将流的所有权“移交”给 StreamWriter 可以解决该特定(常见)情况下的处置问题。

        【讨论】:

          【解决方案4】:

          我提出这个包装类:

          public class BetterStreamWriter : StreamWriter
          {
              private readonly bool _itShouldDisposeStream;
          
              public BetterStreamWriter(string filepath)
                  :base(filepath)
              {
                  _itShouldDisposeStream = true;
              }
          
              public BetterStreamWriter(Stream stream)
                  : base(stream)
              {
                  _itShouldDisposeStream = false;
              }
          
              protected override void Dispose(bool disposing)
              {
                  base.Dispose(disposing && _itShouldDisposeStream);
              }
          }
          

          对象不应该丢弃它们没有实例化的东西。如果它是一个文件流编写器,它应该处理。如果是外部流,则不应该。

          一开始就不应该实现打开文件路径。这违反了单一职责原则,因为对象同时管理文件的写入和生命周期。

          【讨论】:

          • 对象不应处置他们不拥有的东西。除了直接实例化它们(最常见的是通过调用工厂方法)之外,还可以获得对象的所有权。由于工厂方法通常只返回一个对象,因此该方法获取的任何资源都必须归该对象所有。让StreamWriter 获得传入流的所有权,可以让工厂方法合法地构造流并返回封装它的StreamWriter
          • @supercat 嗯没有。我同意你关于“对象不应该处置他们不拥有的东西”的观点。这才是正确的说法。但是,在这种情况下不是。设计 StreamWriter 类本身,您不知道调用者如何处理流,也不应该知道。考虑在多个阅读器中使用流的情况。然后你必须有条件地用 using 语句包装 StreamReader,这取决于你是否要处理它? IDisposable 应该在 using 语句中变形。没有问题。
          • 正确的做法是让StreamReader/StreamWriter的构造函数允许调用者指定所有权是否被转移(他们在.NET的更高版本中这样做)。否则,请考虑如何编写一个方法,该方法应该异步播放从StreamReader 获取的音频数据。播放音频的代码可能对底层流一无所知,构造StreamReader 的代码可能不知道播放代码何时完成。在纯粹为了音频播放而打开流的常见情况下......
          • ...播放代码对StreamReader 的处理也可以处理底层流将是一件好事。 StreamReader 的设计问题是,尽管在大多数用例中它是否处理并不重要,但它确实重要的情况大致分为流无法以任何其他方式轻松处理的情况,以及那些不应该处理流。后一种情况可能更常见,但如果StreamReader 没有清理底层流,则前一种情况会更难处理。
          猜你喜欢
          • 1970-01-01
          • 2015-03-10
          • 2011-08-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-23
          • 2010-12-18
          • 2013-05-24
          相关资源
          最近更新 更多