C#: 对象变量是否应该赋值为null?

54

在C#中,如果你已经完成使用一个对象变量,即使它将要超出范围,是否有必要将其赋值为 null


8个回答

46

不要将其设置为null,否则可能会存在危险和缺陷(考虑到有人可能尝试稍后使用它,但并不意识到它已被设置为null)。只有在有逻辑理由需要将其设置为null时才这样做。


请看下面我的回答。在Dispose()的实现中将成员设置为null有非常好的理由,这可能有助于避免错误。 - Mick
我不确定“易出错”是一个好的论点。如果你在没有阅读中间代码的情况下稍后使用它,引用可能会被重新分配,或者其状态发生改变,因为同样的原因导致你遇到不同的错误。在编辑之前阅读并理解代码,并进行测试是解决这个问题的方法。 - xr280xr

31

在我看来,更重要的是要对实现了IDisposable接口的对象调用Dispose方法。

除此之外,将null赋值给引用变量只意味着您明确表示作用域结束——大多数情况下,这只是一些指令提前执行(例如方法体中的局部变量)——随着编译器/JIT优化的时代,运行时很可能会执行相同的操作,因此您真正得不到任何好处。在一些情况下,例如应用程序级别的静态变量等,如果您已经完成使用某个对象,则应将其赋值为null,以便让垃圾回收机制回收该对象。


3
你的意思是垃圾回收器会检查一个变量是否为null来决定是否对其进行垃圾回收?我认为它只考虑该变量是否仍然处于活动状态/在作用域内。 - Craig Johnston
8
这个PowerPoint演示文稿 http://download.microsoft.com/download/e/2/1/e216b4ce-1417-41af-863d-ec15f2d31b59/DEV490.ppt,从第30页开始展示了JIT/GC一起的作用。这意味着空赋值是特别无意义的。 - Damien_The_Unbeliever
@Damien_The_Unbeliever - 一个很好的资源!感谢你。@Craig,我实际上是想说,即使对象明显不超出作用域,GC也很可能认为它是垃圾,它只关心引用是否被使用。因此,在运行实例方法时,该对象可能会被垃圾回收。请参阅这篇优秀的文章:http://blogs.msdn.com/b/oldnewthing/archive/2010/08/10/10048149.aspx - VinayC
1
@Craig,继续说——对于即将超出作用域的局部变量来说,将其赋值为null是没有用处的。但是对于具有AppDomain级别作用域的静态变量,对象将一直存在,直到静态变量保持引用。因此,如果您不再需要该对象,则可以显式地将其设置为null。 - VinayC
@VinayC - 很抱歉,您所说的在技术上是不正确的。 (C#) 变量的作用域是应用程序代码中可以使用变量标识符(名称)的部分(忽略“访问修饰符”的问题)。作用域不受 null 赋值的影响。当您将 null 分配给变量时,实际上正在潜在地更改变量引用的对象的生命周期。即使“说您不再需要该对象”也是一种过度简化。(您实际上是在说您不需要它在这里。您可能需要它在其他地方!) - Stephen C

31

在将汽车推向湖边之前,您是否应该关闭它的引擎?
不需要。这是一个常见的误解,但并不会有任何影响。您只是将一个引用设置为null,而不是将 对象 设置为null - 对象仍然存在于内存中,必须由垃圾收集器继续回收。


21

这些回复中大多数都给出了正确的答案,但是理由错了。

如果它是一个局部变量,那么在方法结束时该变量将从堆栈中弹出,因此它指向的对象将有一个较少的引用。如果该变量是对象的唯一引用,则该对象可供GC使用。

如果您将变量设置为null(许多人在学习时被教导在方法末尾这样做),则实际上可能会延长对象停留在内存中的时间,因为CLR将认为对象无法收集,直到在下面看到代码引用该对象。但是,如果省略null的设置,则CLR可以确定在代码的某个点之后不再出现对象的更多调用,即使方法尚未完成,GC也可以收集对象。


15

将变量赋值为null通常是一个不好的想法:

  1. 从概念上讲,这没什么意义。
  2. 对于许多变量来说,你可能会有足够多的额外赋值为null,以至于可以显著增加方法的大小。源代码越长,读取它所需的心理努力就越大(即使其中很多都是可以过滤掉的东西),并且更容易发现错误。只有在这样做可以使代码更易于理解时,才将代码变得比必要更冗长。
  3. 你的null赋值可能不会被优化掉。在这种情况下,编译的代码直到达到那个null赋值才会真正释放内存,而在大多数情况下,一旦变量不再执行其他操作而只是超出其范围(有时甚至是在其范围之前),它就可以被释放。因此,你可能会受到非常轻微的性能影响。

唯一我会将某些东西赋值为null以“清除”不再使用的变量,而不是因为null实际上是我想要分配的值的时候,有两种可能情况:

  1. 它是可能长期存在的对象的成员,将不再被该对象使用,并且具有相当大的大小。在这种情况下,将其分配为null是一种优化。
  2. 它是可能长期存在的对象的成员,将不再被该对象使用,并且已经被处理以释放其资源。在这种情况下,将其分配为null是一种安全措施,因为更容易找到一个意外使用了null对象的情况,而不是意外使用了已处理的对象。

这些情况都不适用于局部变量,只适用于成员,而且都很少见。


你的第二种情况更有意义。追踪空引用异常比追踪半处置对象更容易。 - rollsch
1
@rolls 是的,特别是因为使用这样一个过期的对象的影响可能会远离错误的代码。 - Jon Hanna

5

不会。对于本地变量来说,你是否有对象的引用并不影响任何事情,重要的是这个引用是否会被使用。

在代码中加入额外的空赋值并不会对性能造成太大影响,也不会影响内存管理,但它会增加无意义的语句,使代码难以阅读。

垃圾收集器知道引用在代码中最后一次使用的时间,因此它可以在对象不再需要时立即回收它。

示例:

{
  // Create an object
  StringBuilder b = new StringBuilder();
  b.Append("asdf");
  // Here is the last use of the object:
  string x = b.ToString();
  // From this point the object can be collected whenever the GC feels like it
  // If you assign a null reference to the variable here is irrelevant
  b = null;
}

垃圾回收器真的知道正在执行哪些代码吗?它只是对堆栈进行(静态)分析并跟踪所有引用吗? - Olivier Jacot-Descombes
@OlivierJacot-Descombes:当垃圾回收完成时,它确实知道代码使用了哪些变量。您可以通过在彼此之后分配多个巨大的数组并分配给本地变量来测试此功能。尽管堆栈上有引用它们的变量,但第一个数组将被收集以为后面的数组腾出空间。 - Guffa
+1。你是对的。在阅读了Damien_The_Unbeliever提到的PPT演示文稿的第35页之后,我必须同意你的观点。 - Olivier Jacot-Descombes

2
对于必须实现IDisposable的对象,作为一种惯例,我在IDisposable的实现中将所有成员设置为null。
在很久以前,我发现这种做法极大地改善了在Windows Mobile上运行的.NET Compact Framework应用程序的内存消耗和性能。我认为当时的.NET Compact Framework与主要的.NET Framework相比,垃圾收集器的实现非常简单,而在IDisposable的实现中解耦对象有助于.NET Compact Framework上的GC执行其任务。
此做法的另一个原因是,在对对象执行IDisposable后,任何尝试使用已释放对象的任何成员实际上都是不可取的。当然,理想情况下,当某些东西尝试访问已释放对象的任何函数时,你希望从已释放对象中获得ObjectDisposedException,但是与没有异常相比,NullReferenceException更好。你想知道代码是否在处理已释放的对象,因为胡闹已释放的非托管资源可能会使应用程序陷入麻烦。
注意:我绝不主张仅出于将成员设置为null的目的而在对象上实现IDisposable。我所说的是当你需要实现IDisposable以满足其他需求时,例如你的成员实现了IDisposable或者你的对象包装了非托管资源。

0

我只想补充一点,据我所知,这仅适用于 Visual Basic 的一个版本,甚至连那个版本也有争议。(如果我没记错的话,它仅适用于 DAO 对象。)


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