在.NET中,我应该何时处理我的对象?

20

对于一般的代码,我是否真的需要处理对象?我可以忽略它,还是在确保不再需要它时始终处理对象是个好主意?

8个回答

27

在您使用对象后立即处理掉它。可处理的对象代表持有有价值资源的对象,而CLR并不知道这些资源。因此,GC也无法智能地决定何时回收可处理对象,从而释放其底层资源。

最终,GC会感到内存压力,并仅仅是因为偶然而非智能地回收您的对象。如果您不以确定性方式处理可处理的对象,则完全可能进入资源匮乏状态,即使几乎没有内存压力。

以下是一个快速示例,说明如何发生这种情况。假设底层资源是Win32句柄。这些很有限且相当小。您运行了一个创建大量Foo对象的操作。Foo对象实现IDisposable接口,负责创建和处理Win32句柄。它们不是手动释放的,并且通过一种巧合进入Gen2堆。该堆不经常被释放。随着时间的推移,足够多的Foo实例进入Gen2堆中,占用了所有可用的句柄。新的Foo对象因此无法创建,无论使用了多少内存。

事实上,为了释放句柄,需要在单个操作期间分配相当大的内存才能产生足够的压力来释放这些实例。


System.Drawing类不能正确地通知GC内存压力,因此它们没有按照应有的优先级进行处理和清除(它将80MB对象视为1K或更少)。 - Lilith River

15

如果对象实现了IDisposable接口,在使用完后应该立即对其进行处理。最简单的方法是将其包含在using块中:

using (SqlCommand cmd = new SqlCommand(conn)) {
    cmd.ExecuteNonQuery();
}

6

任何实现 IDisposable 接口的类型都应该始终调用 Dispose(),因为通常它被用来表示该类型获取了非托管资源。这些资源特别重要且应尽早释放。正如其他人所提到的,使用 using 是首选方式。


3
有几种方法可以看待这个问题。一种方法是试图确定是否有必要在对象不再需要时立即处理它,例如使用Reflector查看它是否真的持有未管理的资源,或者它们是否已经被意外地处理掉了。另一种观点是假设如果一个对象实现了IDisposable接口,那么你不需要判断Dispose()是否真的需要被调用 - 你总是应该调用它。我认为这是正确的方式。窥探对象的私有实现以决定如何消耗它们,会增加与可能会改变的实现耦合的风险。一个例子是LINQ to SQL DataContext。它实现了IDispose但大多数情况下不需要显式调用Dispose()就可以清理自己。我的偏好是编写显式处理的代码,但其他人建议这并非必要。
当然,这都适用于实现IDisposable接口的对象。GC将在大多数情况下在没有任何显式操作的情况下处理其余的所有内容,但值得阅读一些关于GC行为微妙之处的文章(我现在太累了,想不起来细节),以便知道何时显式处理对象,更重要的是,何时实现IDispose接口。有很多关于这个问题的好文章在互联网上。
正如先前所说,使用(..) { ... }是实现IDisposable接口的好方法。

1
如果该对象实现了IDisposable接口,那么很可能它正在保持对未托管资源的引用。因此,一个经验法则是,在你使用完该对象后,直接或通过using语句块调用Dispose方法释放资源。不要依赖GC(垃圾回收器),因为这正是IDisposable的作用——确定性地释放资源。

0

如果你的类没有持有非托管资源,那么你可以不调用 Dispose 方法。但是,如果你的类持有一个非托管资源,例如需要删除的临时文件,则必须显式地调用 Dispose。

你可以通过在 Finalize 方法中编写释放代码来避免调用 Dispose,但这样你就依赖于垃圾回收器,因为你永远不确定垃圾回收器何时会完成你的对象。为了安全起见,如果你设计了一个持有非托管资源的类,可以在 Dispose 和 Finalize 方法中编写相同的对象释放代码,但如果这样做,请始终在 dispose 方法中使用 SuppressFinalize(),因为如果你的对象已经在终结队列上,它将防止调用 Finalize() 方法。


实际上,最好的方法不是在您的 finalize 方法和 dispose 方法中重复代码,而是按照此链接中所述进行实现 http://msdn.microsoft.com/en-us/library/b1yfkh5e(VS.71).aspx - Sekhat

-1
在大多数情况下,依靠垃圾回收机制“运作”良好。典型的例外是当您进行资源密集型交互时 - 在这种情况下最好显式地处理。
明显的例子。
using (var conn = new SqlConnection(connString)) {}

"

'Using'块绝对是确保对象正确处理的最清洁和最健壮的方法。 'Using'块可以与任何实现IDisposable接口的对象一起使用。

"

例如,当您创建一个位图实例时,您认为不需要手动释放它吗?再想想。垃圾回收器将把这个30MB的对象视为10个字节,并无限期地忽略它。这会导致您的服务器每次都崩溃。 - Lilith River
WPF通过对非托管对象进行人工GC压力估算来避免这种情况,但是它还存在其他错误,使其在服务器上不受支持。 - Lilith River
个人而言,通常在完成后会尝试处理(就像berko所说的那样,使用块...),但这个答案没有问题,因为他是正确的,在非资源密集型情况下(您可以使用IDisposable来处理不安全数据以外的情况),通常并不太重要。我认为他的观点是这是一个判断性的调用。在大多数情况下,包装在“using”中将是正确的选择,但如果它被传递到不同的线程中怎么办?@计算机语言学家 - 在3年后评论“crash your server EST”是具有煽动性的。 - Luke Schafer
根据定义,IDisposable 用于非托管资源。MemoryStream 是一个例外,但是有些 MemoryStream 的子类添加了非托管句柄(用于 COM IStream 互操作),因此你甚至不能完全排除它们。所以,我完全不同意它在大多数情况下都有效的说法。它只是还没有咬到 Berko。我个人帮助过许多用户(被类似的评论误导),他们没有使用 using,并因此导致服务器不断崩溃。如果这个陈述让人感到害怕,那么它也是真实的,并且有数据支持。 - Lilith River
如果某个实例正在被传递,那么某个线程或方需要负责其处理。 - Lilith River
显示剩余3条评论

-7

当你完成一个对象的使用后,你可以忘记它。只要它没有被任何地方引用,那么它就像消失了一样。当垃圾收集器感觉到需要释放内存时,它所占用的内存就会被释放。


有人可以解释一下为什么这个答案会被评价得如此差吗?-6分?而且没有评论? - bobobobo
4
我认为这是因为它没有涉及到IDisposable接口,该接口旨在允许对象在不再需要时释放.NET运行时之外的资源,而不是等待垃圾回收。 - Donald Byrd

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