垃圾回收器不清理哪些对象?

6
一个静态分析工具一直提示我,我的C#代码中存在资源泄漏问题。
以下是一个示例:
StringReader reader = new StringReader(...); 

// do something with reader

...

} // static analysis tool cries that I've leaked **reader**

我的工具正确吗?如果是,为什么?
编辑(回复评论)- 我的静态分析工具显示我有一堆资源泄漏。我从这个论坛了解到,某些Java AWT对象需要显式释放,否则会发生泄漏。是否有需要显式释放的C#对象?

1
什么是静态分析工具?你能更具体一些吗? - AlwaysAProgrammer
9个回答

13

是的,你的代码泄漏得很严重。应该像这样:

using (StringReader reader = new StringReader(...))
{

}

每个实现了 IDisposable 接口的类都需要被包裹在 using 块 中,以确保 Dispose 方法总是被调用。


更新:

详细说明:在.NET中,有一个IDisposable接口,它定义了Dispose方法。实现此接口的类(如文件流、数据库连接、读取器等)可能包含指向非托管资源的指针,确保这些非托管资源/句柄被释放的唯一方法是调用Dispose方法。因此,在.NET中,为了确保即使抛出异常也会调用某些代码,需要使用try/finally语句:

var myRes = new MyResource(); // where MyResource implements IDisposable
try
{
    myRes.DoSomething(); // this might throw an exception
}
finally
{
    if (myRes != null)
    {
        ((IDisposable)myRes).Dispose();
    }
}

编写C#代码的人很快就意识到,每次处理可释放资源时都要编写这个是一件很麻烦的事情。因此,他们引入了using语句:

using (var myRes = new MyResource())
{
    myRes.DoSomething(); // this might throw an exception
}

这有点短。


3
您说它正在严重泄漏,但在StringReader的情况下,实际上不会有任何东西泄漏 - 在这方面它类似于MemoryStream。出于最佳实践和以防您将来更改代码以使用需要清理的类似内容,最好还是处理掉它...但它实际上并没有泄漏。 - Jon Skeet
1
@Jon,是的,但我们在这里教授良好的实践。人们会说这样做没问题,但我已经看腻了这种代码:var writer = new StreamWriter("foo.txt") - Darin Dimitrov
3
@Mike Caron,嗯,稍后再试,你有尝试过使用文件吗? 你是否曾经遇到过“此文件无法打开,因为它已被其他进程使用”异常?请在处理IDisposable资源时每次都使用using。当事情可以变得简单的时候,为什么要让我们的生活更加艰难呢?为什么我们要问那些问题呢?这个世界上没有什么东西会说服我StringReader reader = new StringReader(...);是好代码。如果在C# 9.0中微软引入了一些StringReader的新概念,那么这段代码实际上可能会出现问题并破坏程序。 - Darin Dimitrov
3
我认为教授良好的实践包括诚实 - 特别是,如果你告诉某人他们的代码“漏得很厉害”,而实际上并非如此,他们可能会认为即使在其他情况下它也不会对他们造成影响。这样做会带来麻烦。在我看来,最好既诚实又提供良好的实践建议。 - Jon Skeet
4
希望啤酒不会漏出来,那可是个真正的问题。 - Kevin Meredith
显示剩余7条评论

6
在这种情况下,您的代码实际上并没有泄漏任何内容,因为StringReader在本质上没有任何需要清理的资源,就像MemoryStream一样。 (使用MemoryStream时,如果您正在异步使用它或将其远程化,则仍然可能需要处理它...但在简单情况下,这并不重要。)
然而,出于原则考虑,最好处理任何实现IDisposable的对象。这将避免您泄漏(可能是暂时的)未受管控的资源,例如文件句柄。
例如,假设您将代码更改为:
StreamReader reader = new StreamReader("file.txt");
...

如果你没有在这里关闭或处理reader(在一个finally块或通过using语句),它将一直保持文件打开状态,直到直接持有操作系统文件句柄的类型被最终化。显式地处理对象不仅可以更早地释放非托管资源,还可以将可最终化的对象从最终化队列中移除,这意味着它们可以更早地进行垃圾回收。

3
正如其他人所说的那样,问题在于StringReader没有被处理,所以我不会深入讨论这个问题。
事实上,情况是静态分析工具本质上是一个愚蠢的工具。我不是指它不能使用,而是指它只看到了非常有限的标准。
在这种情况下,分析工具看到实例化了一个实现IDisposable接口的类的对象。然后,该工具只查看您是否在对象离开作用域之前进行了相应的处理调用。这可以通过明确地说出object.Dispose();或通过using(var x =...) {}子句来完成。
根据MS规范,类应在处理非托管资源(如文件句柄)时实现IDisposable接口。现在,您可能需要查看这个MSDN帖子,其中讨论了实现IDisposable接口的哪些类你不一定需要调用dispose()方法。
这留给我们两个可行的解决方案。第一个(也是我和达林推荐的)是始终将实现IDisposable接口的对象包装在using语句中。这只是一个好习惯。毕竟,它不会造成任何伤害,而没有它可能会导致大量的内存泄漏(取决于类),而我记不住哪个是哪个
另一种方法是配置您的静态分析工具(如果可能的话)忽略此类警告。我真的认为这将是一个坏主意(tm)。

2
有许多种流的类别,有相应类型的阅读器对象。其中一些类别以必须在完全放弃之前撤消它们的方式操纵外部对象,但在使用它们时无法撤消(例如,文件阅读器将打开一个文件;在忘记文件句柄之前必须关闭该文件,但在阅读器使用它之前无法关闭)。"Dispose"方法将负责在阅读器完全放弃之前将任何必须“放回”的内容处理好。由于通常情况下,一个代码片段会创建一个流阅读器并将其传递给另一个代码片段,而第一个代码片段可能没有办法知道使用阅读器的代码何时完成,因此使用阅读器的代码有责任在完成后调用Dispose来释放阅读器。
请注意,某些类型的阅读器实际上在其Dispose方法中不执行任何操作,但接受任意类型的流阅读器的任何代码都应该调用它。虽然你的代码可能期望不需要Dispose的流阅读器类型,但它可能会被赋予需要Dispose的派生类。即使不是严格必要的,调用Dispose也将保护免受这种情况的影响。

2

看起来你没有处理StringReader。你需要调用.Dispose()来清理非托管资源。或者更好的方法是,在using块中使用它,如下所示:

using (StringReader reader = new StringReader(...))
{
    // your code
}

这将在代码块结束时自动引发Dispose(),即使您的代码抛出异常(与使用finally块相同)。


1

我相信这是因为你在使用完之后没有调用Close方法,尽管根据MSDN的说法:

Close方法的实现会调用Dispose方法,并传递一个true值。

所以我期望当垃圾回收器处理读取器时,它将被正确释放,不会造成内存泄漏。

更新:我错了,垃圾回收器不会自动释放IDisposable对象。你需要显式地调用Close(或Dispose)方法。


如果正确实现了 IDisposable 模式,那么类终结器 (~Class) 将会被执行,其效果应该与调用 Dispose 相同。当然,这其中还有更多需要解释的内容,但我在此无法详细说明。 - Mike Caron

0

你已经泄漏了,但是“最终”垃圾回收器会为你清理它。


0
垃圾回收器将收集任何不再被引用的内容。在您的示例中,reader 最终会被收集(尽管没有人能够确定何时)。
然而,“静态分析工具”正在抱怨您没有手动调用 Dispose()
StringReader reader = ...
...
reader.Dispose();

在这种特定情况下,这可能不是什么大问题。然而,当处理许多IO类(*Stream,*Reader等)时,最好在完成后将它们处理掉。您可以使用using来帮助:
using(StringReader reader = ...) {
    ...
}  //reader is automatically disposed here

0
一个字符串读取器可能正在访问一个流。如果不处理字符串读取器,你可能会让那个流一直处于打开状态。这个流可能连接到系统上的某个文件,进而导致该文件被锁定。
尝试查看 using 语句,它会自动调用 dispose 方法。
using (sr) {
 // your code
}

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