【问题标题】:Does disposing streamreader close the stream?处理流阅读器会关闭流吗?
【发布时间】:2010-11-07 02:26:05
【问题描述】:

我正在向方法发送一个流以进行写入,并且在这些方法中我使用的是二进制读取器/写入器。当读取器/写入器被using 或仅当它未被引用时,流是否也被关闭??

我会发送一个 BinaryReader/Writer,但我也在使用 StreamReader(也许我应该绕过它。我只将它用于 GetLine 和 ReadLine)。如果每次关闭写入器/读取器时都关闭流,这将非常麻烦。

【问题讨论】:

    标签: c# stream streamreader


    【解决方案1】:

    是的,StreamReaderStreamWriterBinaryReaderBinaryWriter 在您对它们调用 Dispose 时都会关闭/处理它们的底层流。如果读取器/写入器只是被垃圾收集,它们不会处理流 - 您应该始终处理读取器/写入器,最好使用 using 语句。 (事实上​​,这些类都没有终结器,也不应该有。)

    我个人更喜欢对流使用 using 语句。您可以非常整齐地嵌套不带大括号的 using 语句:

    using (Stream stream = ...)
    using (StreamReader reader = new StreamReader(stream, Encoding.Whatever))
    {
    }
    

    即使流的using 语句有些多余(除非StreamReader 构造函数抛出异常)我认为最好的做法是,如果你摆脱StreamReader 并直接使用流以后,您将拥有正确的处置语义。

    【讨论】:

    • 哦,很好,它只在调用 Dispose 时发生,而不是在应该完成时发生。
    • @Nefzen:那是因为无法保证您的对象最终确定的顺序。如果 StreamReader 和底层 Stream 都符合终结条件,则 GC 可能首先终结流 - 然后 streamreader 将没有对流的引用。因此,您只能在 finalize 中释放非托管资源(例如,FileStream 在其 finalize 中关闭其 windows 文件句柄)。哦,当然,如果您从不处置,流最终仍将被收集(并且文件关闭)。不释放流只是一种非常糟糕的做法。
    • 这种嵌套导致 VS 代码分析器抱怨:CA2202 : Microsoft.Usage : Object 'stream' can be disposed more than once in method '...'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object. 应该忽略吗?到目前为止我没有得到任何异常......
    • @H.B.:在这种情况下忽略它是安全的。或者您可以在构造函数调用StreamReader 中创建流。鉴于IDisposable.Dispose 的文档明确指出,警告在我看来是虚假的:“如果多次调用对象的 Dispose 方法,则对象必须忽略第一个调用之后的所有调用。如果它的对象不能抛出异常多次调用 Dispose 方法。”
    • @JonSkeet:其实有一个page for this,你是对的,这是假的:)
    【解决方案2】:

    这是一个旧的,但我今天想做类似的事情,发现事情已经改变了。从 .net 4.5 开始,有一个 leaveOpen 参数:

    public StreamReader( Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen )
    

    唯一的问题是为其他参数设置什么并不完全清楚。这里有一些帮助:

    来自 the msdn page 的 StreamReader 构造函数(流):

    此构造函数将编码初始化为 UTF8Encoding,即 BaseStream 属性使用流参数,以及内部 缓冲区大小为 1024 字节。

    剩下的就是detectEncodingFromByteOrderMarks,根据the source code判断是true

    public StreamReader(Stream stream)
            : this(stream, true) {
    }
    
    public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
            : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize) {
    }
    

    如果其中一些默认值被公开或者参数是可选的,这样我们就可以指定我们想要的那些,那就太好了。

    【讨论】:

    • 非常好的信息!从来没有听说过这个新参数,它实际上很有意义。
    • 对于像我这样的懒人来说,让流打开的简短回答如下:using (var streamReader = new StreamReader(myStream, Encoding.UTF8, true, 1024, true))
    • 你可以使用命名参数来指定你想要的,其余的保持默认值:using (var streamReader = new StreamReader(myStream, leaveOpen: true))
    【解决方案3】:

    是的,确实如此。您可以通过使用 Reflector 查看实现来验证这一点。

    protected override void Dispose(bool disposing)
    {
        try
        {
            if ((this.Closable && disposing) && (this.stream != null))
            {
                this.stream.Close();
            }
        }
        finally
        {
            if (this.Closable && (this.stream != null))
            {    
                this.stream = null;    
                this.encoding = null;
                this.decoder = null;
                this.byteBuffer = null;
                this.charBuffer = null;
                this.charPos = 0;
                this.charLen = 0;
                base.Dispose(disposing);
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      晚了六年,但也许这可能对某人有所帮助。

      StreamReader 在释放时会关闭连接。但是,“使用 (Stream stream = ...){...}”和 StreamReader/StreamWriter 会导致 Stream 被释放两次:(1) 当 StreamReader 对象被释放时 (2) 和 Stream using 块时关闭。这会导致在运行 VS 的代码分析时出现 CA2202 警告。

      另一个直接取自CA2202 页面的解决方案是使用try/finally 块。设置正确,这只会关闭一次连接。

      CA2202 的底部附近,微软建议使用以下内容:

      Stream stream = null;
      try
      {
          stream = new FileStream("file.txt", FileMode.OpenOrCreate);
          using (StreamWriter writer = new StreamWriter(stream))
          {
              stream = null;
              // Use the writer object...
          }
      }
      finally
      {
          stream?.Dispose();
      }
      

      而不是...

      // Generates a CA2202 warning
      using (Stream stream = new FileStream("file.txt", FileMode.Open))
      using (XmlReader reader = new XmlReader (stream))
      {
          // Use the reader object...
      }
      

      【讨论】:

      • 在已接受答案的 cmets 中也讨论了警告。 Jon Skeet 在那里提供了一些建议。
      • 另外,注意 using 语句实际上被编译器转换为 try-finally 块。
      【解决方案5】:

      是的。调用 Dispose() 和 IDisposable(“使用”)应该使对象清理其所有资源。这包括流刷新和关闭其文件描述符。

      如果您想将它传递给其他方法,那么您需要确保这些方法不会在 using 块中进行读取/写入。

      【讨论】:

        【解决方案6】:

        如果需要,解决此问题的一种简单方法是重写 StreamWriter 类的 Dispose 方法。有关如何执行此操作的代码,请参见我的帖子:

        Does .Disposing a StreamWriter close the underlying stream?

        【讨论】:

          【解决方案7】:

          通过“使用”关键字或显式调用 dispose 处理的流

          【讨论】:

            猜你喜欢
            • 2016-01-23
            • 2015-12-25
            • 2013-07-17
            • 1970-01-01
            • 2014-07-11
            • 2014-10-31
            • 1970-01-01
            • 1970-01-01
            • 2020-12-05
            相关资源
            最近更新 更多