通常将值设为null是无关紧要的,非常少有用处,偶尔会有害。
首先,让我们考虑最简单的情况:
private void DoStuff()
{
var newWin = new WinWorkers_AddWorker();
newWin.WindowState = this.WindowState;
newWin.Show();
int irrelevant = 42;
this.whoCares = irrelevant * 7;
int notRelevantEither = irrelevant + 1;
this.stillDontCare = notRelevantEither * irrelevant;
}
在这里,
newWin
仅存在于该方法中;它在其中创建,并且通过返回或分配给具有更广泛范围的成员而未离开该方法的范围。
当许多人询问
newWin
何时被垃圾回收时,他们会告诉你,在带有
this.stillDontCare
的行之后,因为那时
newWin
超出了范围。因此,我们可以在最后一次使用后将
newWin = null
赋值,但这可能是微不足道的。
从概念上讲,这是正确的,因为我们可以添加处理
newWin
的代码,直到那个点,并且
newWin
可供我们使用。
实际上,很可能
newWin
在
.Show()
之后立即变得适合进行收集。虽然在概念上它在此之后仍在作用域内,但实际上它没有被使用,编译器知道这一点。(从现在开始,“编译器”将指生成实际运行代码的整个过程,包括IL编译器和JIT)。由于不再使用
newWin
本身使用的内存(即堆栈上的引用,而不是对象),编译器可以将该内存用于
irrelevant
或其他内容。没有活动引用,对象适合进行收集。
实际上,如果在一个对象上调用的最后几个方法实际上不使用
this
指针(直接或通过使用成员字段),则对象甚至可以在调用这些方法之前被回收,因为它们实际上不使用该对象。如果有一个方法其
this
指针从未被使用过(再次,直接或间接地),那么它可能永远不会被创建!
现在,考虑到这一点,我们可以看到,如果变量在变量超出范围之前赋值为空,它实际上并不会使微不足道的差异。
实际上,这种赋值甚至可能使其变得更难以获得资格,因为如果编译器无法看到变量的使用不会影响对象(不太可能,但如果存在
try...catch...finally
块使分析更加复杂,则可能发生),那么它甚至可能延迟认定对象符合条件的时间点。这也可能是微不足道的,但它确实存在。
到目前为止都很简单;如果我们不管它,好事情就会发生,并且让它自然而然地留下来很容易。
然而,将引用设置为null可能会有所好处。考虑以下内容:
public class SomeClass
{
private WorkerThing _newWin;
private void DoStuff()
{
_newWin = new WinWorkers_AddWorker();
_newWin.WindowState = this.WindowState;
_newWin.Show();
}
}
考虑到这里,在调用
DoStuff()
后,
_newWin
被存储在成员变量中。它不会在
SomeClass
实例超出范围之前失效。那什么时候会发生呢?
好吧,我不能回答这个问题,但有时答案很重要。如果
SomeClass
本身也是短暂的,那就无所谓了。它很快就会失效,带着
_newWin
一起失效。然而,如果我们将
_newWin = null
赋值,则该对象将立即有资格进行收集。
现在,这里有一些重要的注意事项:
- 首先,没有充分的理由使
_newWin
成为成员变量。如果上面的示例是完整的代码,我们将把它移回到DoStuff()
中,并且不仅在效率方面获得收益,而且在正确性方面获得更多收益,因为我们不能从另一个成员对_newWin
做出愚蠢的操作。
- 如果我们将某些东西保存在成员变量中,那么可能有很好的理由。这个好理由将优先于尽快清除变量。
- 大多数对象本身并不占用太多内存。偶尔出现一个成员变量也不会有什么影响。
因此,将null分配给成员变量的主要原因是因为null已成为最适当的值。将null分配给不再使用的成员通常不是为了尽快释放其内存,而是因为不再适合使用,当它为null时,这一点变得不可能,并且向代码的其余部分清楚地发出信号。
如果引用的生命周期比方法长(因此放在成员变量中),并且比包含对象的生命周期明显短,并且消耗非常大的内存,那么将null赋值开始变得有意义。在极为罕见的情况下,当这种组合发生时,我们可能想将其分配为null,以表明它不再可供类使用,因此我们仍然不会将null分配为
释放到GC的目的。这几乎是不可能的,但真的“不行”。