我应该为流对象调用 Close() 或 Dispose() 吗?

Posted

技术标签:

【中文标题】我应该为流对象调用 Close() 或 Dispose() 吗?【英文标题】:Should I call Close() or Dispose() for stream objects? 【发布时间】:2011-11-23 09:43:33 【问题描述】:

StreamStreamReaderStreamWriter等类实现IDisposable接口。这意味着,我们可以在这些类的对象上调用Dispose() 方法。他们还定义了一个名为Close()public 方法。现在这让我感到困惑,一旦我完成了对象,我应该调用什么?如果我同时打电话怎么办?

我当前的代码是这样的:

using (Stream responseStream = response.GetResponseStream())

   using (StreamReader reader = new StreamReader(responseStream))
   
      using (StreamWriter writer = new StreamWriter(filename))
      
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            
               writer.Write(buffer, 0, count);
            
         
         writer.Close();
      
      reader.Close();
   

如您所见,我编写了using() 构造,它会自动在每个对象上调用Dispose() 方法。但我也调用Close() 方法。对吗?

请向我推荐使用流对象时的最佳做法。 :-)

MSDN 示例不使用using() 构造,而是调用Close() 方法:

How to: Download Files with FTP

好吃吗?

【问题讨论】:

如果您使用的是 ReSharper,您可以将其定义为模式目录中的“反模式”。 ReSharper 会将每个用法标记为与您的定义相关的错误/提示/警告。还可以定义 ReSharper 必须如何针对此类事件应用 QuickFix。 提示:您可以对多个一次性iten使用类似的 using 语句: using (Stream responseStream = response.GetResponseStream()) using (StreamReader reader = new StreamReader(responseStream)) using (StreamWriter writer = new StreamWriter(filename)) //...一些代码 Does Stream.Dispose always call Stream.Close (and Stream.Flush)的可能重复 你不需要像这样嵌套你的 using 语句,你可以将它们堆叠在一起并有一组括号。在另一篇文章中,我建议对代码 sn-p 进行编辑,如果您想查看并修复您的“代码箭头”,则该代码应该使用该技术的 using 语句:***.com/questions/5282999/… @Suncat2000 您可以有多个 using 语句,但不能嵌套它们,而是堆叠它们。我的意思不是像这样限制类型的语法:using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) 。我的意思是这样你可以重新定义类型:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) 【参考方案1】:

不,您不应该手动调用这些方法。在using 块的末尾,会自动调用Dispose() 方法来释放非托管资源(至少对于标准的.NET BCL 类,如流、读取器/写入器......)。所以你也可以这样写代码:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 
                     writer.Write(buffer, 0, count);
                 
            
         

Close() 方法调用Dispose()

【讨论】:

我很确定你不需要成为using 第一个responseStream,因为它被reader 包裹着,这将确保它在阅读器被处置时关闭。 +1 仍然 当您说The Close method calls Dispose... 时,这令人困惑。在您的其余帖子中,您暗示Dispose() 会调用Close(),我不应该手动调用后者。你是说他们互相打电话吗? 糟糕的答案。它假定您可以使用 using 块。我正在实现一个不时写入的类,因此不能。 @Jez 你的类应该实现 IDisposable 接口,并且可能还实现 Close() if close is standard terminology in the area,这样使用你的类的类就可以使用 using(或者,再次使用 Dispose 模式)。 OP 询问有关正确关闭流对象的问题。不是一些语法糖。【参考方案2】:

快速跳转到 Reflector.NET 显示 StreamWriter 上的 Close() 方法是:

public override void Close()

    this.Dispose(true);
    GC.SuppressFinalize(this);

StreamReader 是:

public override void Close()

    this.Dispose(true);

StreamReader 中的 Dispose(bool disposing) 覆盖是:

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;
            /* deleted for brevity */
            base.Dispose(disposing);
        
    

StreamWriter 方法类似。

因此,阅读代码很明显,您可以根据需要以任意顺序在流上调用Close()Dispose()。它不会以任何方式改变行为。

因此归结为使用Dispose()Close() 和/或using ( ... ) ... 是否更具可读性。

我个人的偏好是应该尽可能使用using ( ... ) ... ,因为它可以帮助你“不要用剪刀跑”。

但是,虽然这有助于正确性,但它确实会降低可读性。在 C# 中,我们已经有过多的右花括号了,那么我们如何知道哪一个实际上在流上执行了关闭操作呢?

所以我认为最好这样做:

using (var stream = ...)

    /* code */

    stream.Close();

它不会影响代码的行为,但确实有助于提高可读性。

【讨论】:

"在 C# 中,我们已经有过多的闭合花括号,那么我们如何知道哪个实际在流上执行闭合?" 我不认为这是一个大问题:流在“正确的时间”关闭,即当变量超出范围并且不再需要时。 嗯,不,那是“他为什么要关闭它两次??”阅读时减速带。 我不同意多余的Close() 电话。如果没有经验的人查看代码并且不知道using,他会:1) 查找并学习,或2) 盲目地手动添加Close()。如果他选择 2),也许其他开发人员会看到多余的 Close(),而不是“笑”,指导经验不足的开发人员。我不赞成让没有经验的开发者过日子,但我赞成把他们变成有经验的开发者。 如果你使用 using + Close() 并打开 /analyze,你会得到“警告:CA2202:Microsoft.Usage:对象 'f' 可以在方法 'Foo(string) 中多次处理'。为避免生成 System.ObjectDisposedException,您不应在一个对象上多次调用 Dispose。:Lines: 41" 因此,根据文档和 /analyze,虽然当前的实现可以调用 Close 和 Dispose,但这是不行的并且可能会在 .net 的未来版本中发生变化。 +1 以获得好的答案。另一件要考虑的事情。为什么不在像 //Close 这样的右大括号之后添加评论,或者像我一样,作为一个新手,我在任何不清楚的右大括号之后添加一个衬里。例如,在一个长类中,我将在最后一个右大括号之后添加 //End Namespace XXX,并在第二个最后一个右大括号之后添加 //End Class YYY。这不是 cmets 的用途吗?只是好奇。 :) 作为一个新手,我看到了这样的代码,这就是我来这里的原因。我确实问过“为什么需要第二次关闭”这个问题。我觉得额外的代码行不会增加清晰度。对不起。【参考方案3】:

文档说这两种方法是等价的:

StreamReader.Close:这个 Close 实现调用 Dispose 方法并传递一个真值。

StreamWriter.Close: 这个 Close 实现调用 Dispose 方法,传递一个真值。

Stream.Close:该方法调用Dispose,指定true释放所有资源。

所以,这两个都同样有效:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename))  
   // do something
 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try 
    // do something

finally 
    writer.Close();

就个人而言,我会坚持第一个选项,因为它包含较少的“噪音”。

【讨论】:

【参考方案4】:

在许多同时支持Close()Dispose() 方法的类上,这两个调用是等价的。但是,在某些类上,可以重新打开已关闭的对象。一些这样的类可能会在关闭后保留一些资源,以允许重新打开;其他人可能不会在Close() 上保留任何资源,但可能会在Dispose() 上设置标志以明确禁止重新打开。

IDisposable.Dispose 的合约明确要求在永远不会再次使用的对象上调用它在最坏的情况下是无害的,因此我建议在每个 IDisposable 上调用 IDisposable.Dispose 或称为 Dispose() 的方法对象,是否也调用Close()

【讨论】:

仅供参考,这是 MSDN 博客上的一篇文章,解释了关闭和处置的乐趣。 blogs.msdn.com/b/kimhamil/archive/2008/03/15/…【参考方案5】:

这是一个老问题,但您现在可以编写 using 语句而无需阻止每个语句。当包含块完成时,它们将被以相反的顺序处理。

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)

    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    
        writer.Write(buffer, 0, count);
    

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using

【讨论】:

【参考方案6】:

对于它的价值,the source code for Stream.Close 解释了为什么有两种方法:

// Stream used to require that all cleanup logic went into Close(),
// which was thought up before we invented IDisposable.  However, we
// need to follow the IDisposable pattern so that users can write
// sensible subclasses without needing to inspect all their base
// classes, and without worrying about version brittleness, from a
// base class switching to the Dispose pattern.  We're moving
// Stream to the Dispose(bool) pattern - that's where all subclasses
// should put their cleanup now.

简而言之,Close 存在只是因为它早于Dispose,并且出于兼容性原因无法删除。

【讨论】:

以上是关于我应该为流对象调用 Close() 或 Dispose() 吗?的主要内容,如果未能解决你的问题,请参考以下文章

我应该在 urllib.urlopen() 之后调用 close() 吗?

我应该在 urllib.urlopen() 之后调用 close() 吗?

我必须在调用* sql.Tx.Rollback()之前调用* sql.Rows.Close()吗?

java搜索“可关闭”对象,其中未调用close

pb halt以后,不会调用application的close事件的问题

`io_context.stop()` 与 `socket.close()`