你需要处理的对象,并将其设置为空?

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

对象将在不再使用时以及垃圾收集器看起来合适时清理。 有时,您可能需要将对象设置为null ,以使其超出范围(例如,您不再需要的静态字段),但总体上通常不需要将其设置为null

关于处置对象,我同意@Andre。 如果对象是IDisposable那么在不再需要的时候处理它是一个好主意 ,特别是如果对象使用非托pipe资源。 不处置非托pipe资源将导致内存泄漏

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

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

这在function上等同于:

 MyIDisposableObject obj; try { obj = new MyIDisposableObject(); } finally { if (obj != null) { ((IDisposable)obj).Dispose(); } } 

在C#中,对象不会像在C ++中那样超出范围。 垃圾收集器不再使用时会自动处理垃圾收集器。 这是一个比C ++更复杂的方法,其中variables的范围是完全确定的。 CLR垃圾回收器主动处理所有已经创build的对象,如果它们正在使用的话。

一个对象可以在一个函数中“超出范围”,但是如果它的值被返回,那么GC会查看调用函数是否保留在返回值上。

将对象引用设置为null是不必要的,因为垃圾回收工作是通过计算出哪些对象被其他对象引用的。

在实践中,你不必担心破坏,它只是工作,这是伟大的:)

Dispose完毕后,必须对实施IDisposable所有对象调用Dispose 。 通常情况下,你会使用这样的对象using块:

 using (var ms = new MemoryStream()) { //... } 

编辑variables范围。 Craig询问variables作用域是否对对象生存期有任何影响。 为了正确解释CLR的这个方面,我需要从C ++和C#中解释一些概念。

实际variables范围

在这两种语言中,variables只能在与定义相同的范围内使用 – 类,函数或由大括号括起来的语句块。 但是,细微的差别是,在C#中,variables不能在嵌套块中重新定义。

在C ++中,这是完全合法的:

 int iVal = 8; //iVal == 8 if (iVal == 8){ int iVal = 5; //iVal == 5 } //iVal == 8 

在C#中,但是你会得到一个编译器错误:

 int iVal = 8; if(iVal == 8) { int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else } 

如果您查看生成的MSIL,这是有道理的 – 函数使用的所有variables都是在函数的开头定义的。 看看这个function:

 public static void Scope() { int iVal = 8; if(iVal == 8) { int iVal2 = 5; } } 

以下是生成的IL。 请注意,在if块中定义的iVal2实际上是在函数级定义的。 实际上,这意味着就variables生命周期而言,C#只有类和函数级的范围。

 .method public hidebysig static void Scope() cil managed { // Code size 19 (0x13) .maxstack 2 .locals init ([0] int32 iVal, [1] int32 iVal2, [2] bool CS$4$0000) //Function IL - omitted } // end of method Test2::Scope 

C ++范围和对象的生命周期

每当在堆栈上分配的C ++variables超出范围,就会被破坏。 请记住,在C ++中,您可以在堆栈或堆上创build对象。 当你在堆栈上创build它们时,一旦执行离开作用域,它们会从堆栈popup并被破坏。

 if (true) { MyClass stackObj; //created on the stack MyClass heapObj = new MyClass(); //created on the heap obj.doSomething(); } //<-- stackObj is destroyed //heapObj still lives 

当在堆上创buildC ++对象时,它们必须被明确销毁,否则就是内存泄漏。 没有这样的问题堆栈variables虽然。

C#对象生命周期

在CLR中,对象(即引用types) 始终在托pipe堆上创build。 对象创build语法进一步加强了这一点。 考虑这个代码片段。

 MyClass stackObj; 

在C ++中,这将在堆栈上的MyClass上创build一个实例并调用其默认构造函数。 在C#中它会创build一个类MyClass的引用,它不指向任何东西。 创build一个类的实例的唯一方法是使用new运算符:

 MyClass stackObj = new MyClass(); 

从某种意义上说,C#对象很像使用C ++中的new语法创build的对象 – 它们是在堆上创build的,但与C ++对象不同,它们由运行时pipe理,因此您不必担心会破坏它们。

由于对象总是在堆上,对象引用(即指针)超出范围的事实变得没有意义。 确定是否要收集对象的因素比简单地存在对象的引用要多。

C#对象引用

Jon Skeet 将Java中的对象引用与连接到气球(即对象)的string进行了比较。 同样的类比适用于C#对象引用。 他们只是指向包含对象的堆的位置。 因此,将其设置为null对对象生存期没有直接影响,气球继续存在,直到GC“popup”它。

继续下去气球的类比,似乎合乎逻辑的是,一旦气球没有连接到它的琴弦,它可以被破坏。 事实上,这就是引用计数对象在非托pipe语言中的工作方式。 除了这种方法不适用于循环引用。 想象一下,两个气球通过一个string连接在一起,但是任何一个气球都没有string。 在简单的参考计数规则下,即使整个气球组是“孤儿”的,它们也都会继续存在。

.NET对象就像屋顶下的氦气球。 当天窗打开(GC运行)时,即使可能有一组气球被束缚在一起,未使用的气球也会飘走。

.NET GC使用分代GC和标记和扫描的组合。 分代方法涉及运行时偏好检查最近分配的对象,因为它们更可能是未使用的,标记和扫描涉及运行时贯穿整个对象图并计算出是否存在未使用的对象组。 这充分处理了循环依赖问题。

另外,.NET GC在另一个线程(所谓的终结器线程)上运行,因为它有相当多的工作要做,在主线程上这样做会中断你的程序。

正如其他人所说,如果类实现了IDisposable你一定要调用Dispose 。 我对此持相当严格的立场。 有些人可能会声称,在DataSet上调用Dispose是没有意义的,因为他们反编译它,发现它没有任何意义。 但是,我认为这个论点中有很多谬论。

阅读这个由受尊重的人在这个问题上有趣的辩论。 然后在这里阅读我的推理,为什么我认为杰弗里·里希特是在错误的营地。

现在,你是否应该设置一个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引用的对象有资格collections呢? 如果你在打电话给a = null之后说你错了。 如果你在Main方法完成后说,那么你也是错的。 正确的答案是 DoSomething的调用期间有资格收集。 那是对的。 在引用被设置为null之前,甚至在DoSomething的调用完成之前 ,它是符合条件的。 这是因为JIT编译器可以识别什么时候对象引用不再被解除引用,即使它们仍然是根。

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

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

如果对象实现了IDisposable ,那么是的,你应该处理它。 该对象可能挂在本地资源(文件句柄,操作系统对象),否则可能不会被立即释放。 这可能导致资源匮乏,文件locking问题以及其他可能被避免的微妙错误。

另请参阅在MSDN上实现Dispose方法 。

我同意这里的普遍答案是,你应该处置,不,你通常不应该把variables设置为空…但我想指出,处置不主要是内存pipe理。 是的,它可以帮助(甚至有时)利用内存pipe理,但主要目的是让你确定性地释放稀缺资源。

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

我已经看到了这个问题的真实世界的例子。 例如,我已经看到ASP.Net Web应用程序最终无法连接到数据库(尽pipe很短的时间,或直到Web服务器进程重新启动),因为SQL Server的连接池已满…即,所以创build了很多连接,并且在如此短的时间内没有显式释放,以至于不能创build新的连接,并且池中的许多连接(虽然不是活动的)仍然被未知和未收集的对象引用,不要重复使用。 在必要的情况下正确地configuration数据库连接可以确保不会发生这种问题(至less除非您拥有非常高的并发访问权限)。

如果他们实现了IDisposable接口,那么你应该把它们处理掉。 垃圾收集器将照顾其余的。

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

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

通常情况下,不需要将字段设置为空。 我总是build议处置非托pipe资源。

从经验来看,我还build议你做下面的事情:

  • 退出事件,如果你不再需要它们。
  • 如果不再需要,则将包含委托或expression式的任何字段设置为空。

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

Dispose()是一个很好的地方,但通常会更好。

一般来说,如果一个对象存在一个引用,那么垃圾收集器(GC)可能需要花费几代才能发现一个对象不再被使用。 一直这个对象留在记忆中。

这可能不是一个问题,直到你发现你的应用程序使用了比你想象的更多的内存。 当发生这种情况时,挂上一个内存分析器来查看哪些对象没有被清理。 将引用其他对象的字段设置为null并清除处置集合可以真正帮助GC找出它可以从内存中删除的对象。 GC将更快地回收使用过的内存,使您的应用程序减less内存饥饿和速度。

总是打电话处置。 这是不值得的风险。 大型托pipe企业应用程序应该受到尊重。 没有任何假设可以做,否则它会回来咬你。

不要听话。

很多对象实际上并没有实现IDisposable,所以你不必担心它们。 如果他们真的超出范围,他们将被自动释放。 另外,我从来没有遇到过,我必须设置为空。

有一件事情可以发生的是,许多对象可以被打开。 这可以大大增加您的应用程序的内存使用情况。 有时候很难确定这实际上是一个内存泄漏,还是你的应用程序正在做很多事情。

内存configuration工具可以帮助这样的事情,但它可能会很棘手。

此外,总是退订不需要的事件。 还要小心WPF绑定和控件。 不是通常的情况,但我遇到了一个情况,我有一个WPF控件绑定到一个基础对象。 底层的对象很大,占用了大量的内存。 WPF的控制权正在被一个新的实例取代,旧的仍然由于某种原因而四处闲逛。 这造成了大量的内存泄漏。

在后台代码写得不好,但重点是你要确保没有使用的东西超出范围。 这个花了很长时间才能find内存分析器,因为很难知道内存中的内容是否有效,什么不应该在那里。

当一个对象实现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 

我也必须回答。 JIT通过对variables用法的静态分析生成代码。 这些表项是当前堆栈帧中的“GC-Roots”。 随着指令指针前进,这些表条目变得无效并为垃圾收集做好了准备。 因此:如果它是一个范围variables,则不需要将其设置为null – GC将收集该对象。 如果它是一个成员或一个静态variables,你必须将其设置为null

在这个关于.NET Rocks的节目中,关于这个主题(以及Dispose模式背后的历史)有一个很好的讨论!

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