我应该为流对象调用Close()还是Dispose()方法?

197

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()方法:

这样做好吗?


3
小贴士:您可以像这样对多个可处置项使用using语句: using (Stream responseStream = response.GetResponseStream()) using (StreamReader reader = new StreamReader(responseStream)) using (StreamWriter writer = new StreamWriter(filename)) { //...一些代码 } - Latrova
1
@TimothyGonzalez,你必须像那样嵌套你的using语句。即使在同一条语句中初始化多个资源,using也只允许一个类型。如果您使用多个语句或多个类型,则必须嵌套using语句;这里,对象是不同类型的,必须在单独的using语句中。 - Suncat2000
2
@Suncat2000,你可以有多个using语句,但不要嵌套它们,而是将它们堆叠。我不是指像这样限制类型的语法:using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }。我是指像这样重新定义类型的语法:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { } - Timothy Gonzalez
1
@TimothyGonzalez 抱歉挑剔,但是那些后面的语句确实是嵌套的。 - Suncat2000
显示剩余2条评论
7个回答

143

快速查看 Reflector.NET 可以发现 StreamWriterClose() 方法为:

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();
}

它不会影响代码的行为,但可以增强可读性。


27
在 C# 中,我们已经有大量的右花括号,那么我们如何知道哪个右花括号实际上是关闭流的操作?我认为这不是一个大问题:流会在“正确的时间”关闭,即当变量超出作用域并不再需要时。 - Heinzi
129
嗯,不对,这是在阅读时“他为什么要关闭两次?”的一个“绊脚石”。 - Hans Passant
72
我不同意多余的 Close() 调用。如果有经验较少的人看代码,不知道 using 的话,他会:1) 查找并 学习,或者2) 盲目地手动添加一个 Close()。如果他选择了 2),也许其他开发人员会看到多余的 Close(),而不是“嘲笑”,而是 指导 经验不足的开发人员。我不赞成让经验不足的开发人员的生活变得困难,但我支持将他们变成经验丰富的开发人员。 - R. Martinho Fernandes
22
如果您使用 using + Close() 并打开 /analyze,就会得到警告信息:"warning : CA2202 : Microsoft.Usage : Object 'f' can be disposed more than once in method 'Foo(string)'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.: Lines: 41"。虽然目前的实现对调用 Close 和 Dispose 是可以接受的,但根据文档和 /analyze,它并不是完全正确的,且在未来版本的 .net 中可能会发生更改。 - marc40000
4
对好答案点个赞。还有一件事要考虑,为什么不在右括号后添加注释,例如//Close或者像我这样,作为新手,在任何不清晰的右括号后都加上一句话说明。例如在一个长类中,我会在最终的右括号后添加//End Namespace XXX,然后在第二个最终的右括号后添加//End Class YYY。这不是注释的用途吗?只是出于好奇。 :) 作为新手,我看到了这样的代码,所以来到了这里。我确实问过“为什么需要第二个右括号”。我认为额外的代码行并没有增加清晰度。 - Francis Rodgers
显示剩余16条评论

58
不,你不应该手动调用那些方法。在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()


1
我非常确定你不需要使用第一个“responseStream”,因为它被“reader”包装,当读取器被处理时,它会确保其关闭。尽管如此,还是要点赞。 - Isak Savo
7
糟糕的回答。它假设您可以使用“using”块。我正在实现一个不时写入的类,因此无法使用该方法。 - Jez
8
你的类应该实现IDisposable接口,如果"Close"是该领域的标准术语,则还可能需要实现Close()方法,以便使用你的类的其他类可以使用using(或者采用Dispose模式)。 - Dorus
1
OP 询问如何正确关闭流对象,而不是关于某些语法糖的问题。 - GuardianX
如果流不限于本地范围,则使用using无济于事。下面的答案正确地指出,应该使用.Dispose而不是已过时的.Close方法,因为后者是在IDisposable之前创建的。 - eriyg
显示剩余2条评论

19

文档表示这两种方法是等效的:

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

StreamWriter.Close: 这个 Close 实现会调用 Dispose 方法并传递 true 值。

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();
}

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


19

就算不值钱, 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早存在,并且由于兼容性原因无法删除。

15

这是一个老问题,但是现在(C# 8.0)你可以编写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://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using


哇。谢谢警告!说得真令人困惑。查找处理流的闭合括号已经是足够麻烦的了。现在我们还需要猜测它是否可以保持打开直到函数结束?他们当时在想些什么呢?微软开发人员:自20世纪以来一直在制造尖锐边缘! - Suncat2000

7
在许多支持Close()Dispose()方法的类中,这两个调用是等效的。然而,在某些类上,有可能重新打开已关闭的对象。一些这样的类可能会在关闭后保留一些资源,以便允许重新打开;其他类可能不会在Close()上保留任何资源,但可能会在Dispose()上设置一个标志,以明确禁止重新打开。 IDisposable.Dispose的合同明确要求,在永远不会再使用的对象上调用它最多是无害的,因此我建议对每个IDisposable对象调用IDisposable.Dispose或一个名为Dispose()的方法,无论是否也调用Close()

这是一篇关于 MSDN 博客上解释 Close 和 Dispose 函数的文章。FYI。http://blogs.msdn.com/b/kimhamil/archive/2008/03/15/the-often-non-difference-between-close-and-dispose.aspx - JamieSee

-1

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