你需要处理对象并将它们设置为null吗?

时间:2010-05-28 05:45:31

标签: c# .net garbage-collection dispose

你是否需要处理对象并将它们设置为null,或者当垃圾收集器超出范围时它们是否会将它们清理干净?

12 个答案:

答案 0 :(得分:221)

当不再使用对象以及垃圾收集器认为合适时,将清理对象。有时,您可能需要将对象设置为null以使其超出范围(例如您不再需要其值的静态字段),但总体而言通常无需设置为{{ 1}}。

关于处置对象,我同意@Andre。如果对象是null,那么当您不再需要它时,是一个好主意,特别是如果对象使用非托管资源。不处理非托管资源会导致导致内存泄漏

一旦程序离开IDisposable语句的范围,您就可以使用using语句自动处理对象。

using

在功能上等同于:

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

答案 1 :(得分:129)

答案 2 :(得分:17)

正如其他人所说,如果该类实现Dispose,你肯定想要调用IDisposable。我对此采取了相当严格的立场。例如,有些人可能会声称在Dispose上调用DataSet毫无意义,因为他们反汇编它并发现它没有做任何有意义的事情。但是,我认为这个论点中存在很多谬论。

阅读this,由受尊敬的个人就此问题进行有趣的辩论。然后阅读我的推理here为什么我认为Jeffery Richter在错误的阵营中。

现在,您是否应该设置对null的引用。答案是不。让我用以下代码说明我的观点。

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

那么您认为a引用的对象何时符合收集条件?如果您在致电a = null之后说过,那么您错了。如果您在Main方法完成之后说过,那么您也错了。正确的答案是,在<{>>期间 期间,它有资格收集。DoSomething。那是对的。该参考设置为null,甚至可能在DoSomething的调用完成之前 。这是因为JIT编译器可以识别对象引用何时不再解除引用,即使它们仍然是有根的。

答案 3 :(得分:13)

您永远不需要在C#中将对象设置为null。编译器和运行时将负责确定它们何时不在范围内。

是的,你应该处理实现IDisposable的对象。

答案 4 :(得分:11)

如果对象实现IDisposable,那么是的,你应该处理它。该对象可能会挂起到本机资源(文件句柄,OS对象),否则可能无法立即释放。这可能导致资源匮乏,文件锁定问题以及其他可以避免的微妙错误。

另请参阅MSDN上的Implementing a Dispose Method

答案 5 :(得分:11)

我同意这里的常见答案,是的,你应该处置,并且通常不应该将变量设置为null ...但我想指出dispose主要不是关于内存管理。是的,它可以帮助(有时确实)内存管理,但它的主要目的是为您提供确定性的稀缺资源发布。

例如,如果您打开硬件端口(例如串行),TCP / IP套接字,文件(独占访问模式)甚至数据库连接,您现在已阻止任何其他代码使用这些项目,直到它们为止被释放。处理通常会释放这些物品(以及GDI和其他“os”手柄等,其中有1000种可用,但总体上仍然有限)。如果您没有在所有者对象上调用dipose并显式释放这些资源,那么尝试将来再次打开相同的资源(或另一个程序),打开尝试将失败,因为未处理的未收集对象仍然打开该项目。当然,当GC收集项目时(如果Dispose模式已经正确实现),资源将被释放...但你不知道什么时候会发布,所以你不知道何时可以安全打开那个资源。这是Dispose解决的主要问题。当然,释放这些句柄通常也会释放内存,永远不会释放它们可能永远不会释放内存...因此所有关于内存泄漏或内存延迟清理的讨论。

我已经看到了这个导致问题的现实世界的例子。例如,我看到ASP.Net Web应用程序最终无法连接到数据库(尽管很短的时间,或者直到Web服务器进程重新启动),因为sql server的“连接池已满”...即,如此多的连接已经创建,并且在如此短的时间内没有明确释放,因此无法创建新的连接,并且池中的许多连接虽然不活动,但仍然被未填充和未收集的对象引用,因此可以'重复使用。在必要时正确处理数据库连接可确保不会发生此问题(至少除非您具有非常高并发访问权限)。

答案 6 :(得分:9)

如果他们实现了IDisposable接口,那么你应该处理它们。垃圾收集器将负责其余的工作。

编辑:最好是在处理一次性物品时使用using命令:

using(var con = new SqlConnection("..")){ ...

答案 7 :(得分:4)

通常,不需要将字段设置为null。但我总是建议处理非托管资源。

根据经验,我还建议您执行以下操作:

  • 如果您不再需要,请取消订阅活动。
  • 如果不再需要,则将包含委托或表达式的任何字段设置为null。

我遇到一些非常难以找到的问题,这些问题是不遵循上述建议的直接结果。

这样做的好地方是Dispose(),但通常会更快。

通常,如果对象存在引用,则垃圾收集器(GC)可能需要更长的时间才能确定对象不再使用。对象一直留在记忆中。

在您发现应用程序使用的内存比预期的要多得多之前,这可能不是问题。发生这种情况时,请连接内存分析器以查看哪些对象未被清除。将引用其他对象的字段设置为null并清除处理集合可以真正帮助GC确定它可以从内存中删除哪些对象。 GC将更快地回收使用的内存,使您的应用程序的内存消耗更少,速度更快。

答案 8 :(得分:3)

始终致电处置。这不值得冒风险。应尊重大型托管企业应用程序。不能做任何假设,否则它会回来咬你。

不要听leppie。

许多对象实际上并没有实现IDisposable,因此您不必担心它们。如果它们真的超出范围,它们将自动释放。此外,我从未遇到过必须将某些内容设置为null的情况。

可能发生的一件事是许多物品可以被打开。这可以大大增加应用程序的内存使用量。有时很难弄清楚这实际上是内存泄漏,还是你的应用程序只是在做很多事情。

内存配置文件工具可以帮助解决这类问题,但这可能很棘手。

此外,始终取消订阅不需要的活动。还要小心WPF绑定和控件。不是通常的情况,但我遇到了一种情况,我有一个绑定到底层对象的WPF控件。底层对象很大,占用了大量内存。 WPF控件正在被一个新实例取代,旧的实例仍然因为某些原因而闲置。这导致了大量的内存泄漏。

在hindsite中,代码编写得很糟糕,但重点是你要确保未使用的代码超出范围。那个花了很长时间才找到内存分析器,因为很难知道内存中的东西是有效的,什么不应该存在。

答案 9 :(得分:3)

当一个对象实现IDisposable时,你应该调用Dispose(或Close,在某些情况下,会为你调用Dispose)。

您通常不必将对象设置为null,因为GC将知道不再使用对象。

将对象设置为null时有一个例外。当我检索需要处理的大量对象(来自数据库)时,将它们存储在集合(或数组)中。当“工作”完成后,我将对象设置为null,因为GC不知道我已经完成了它。

示例:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called

答案 10 :(得分:2)

我也要回答。 JIT生成表以及来自变量使用的静态分析的代码。 这些表条目是当前堆栈帧中的“GC-Roots”。随着指令指针的前进,这些表条目变得无效,因此可以进行垃圾收集。 因此:如果它是作用域变量,则不需要将其设置为null - GC将收集该对象。 如果它是成员或静态变量,则必须将其设置为null

答案 11 :(得分:0)

在这一集.NET Rocks中,对这个主题进行了很好的讨论(以及Dispose模式背后的历史)!

http://www.dotnetrocks.com/default.aspx?showNum=10