变量= null作为“对象破坏”来自何处?

我在不同的公司中使用不同版本的.NET编写的许多遗留系统,我一直在寻找以下模式的示例:

public void FooBar() { object foo = null; object bar = null; try { foo = new object(); bar = new object(); // Code which throws exception. } finally { // Destroying objects foo = null; bar = null; } } 

对于任何知道内存管理如何在.NET中工作的人来说,这种代码是非常不必要的; 垃圾收集器不需要您手动分配null来告诉可以收集旧对象,也不需要分配null指示GC立即收集对象。

这种模式只是噪音,因此很难理解代码试图实现的目标。

那么,为什么我一直在寻找这种模式呢? 是否有一所学校教授这种做法? 是否存在一种语言,其中需要为本地范围的变量分配null值才能正确管理内存? 是否有一些额外的值明确指定null我还没有认识到?

开发人员习惯于“免​​费”资源,糟糕的GC实施和糟糕的API,这是FUD 货运狂热编程 (感谢Daniel Earwicker )。

一些GC没有很好地处理循环引用。 要摆脱它们,你必须“在某个地方”打破这个循环。 哪里? 好吧,如果有疑问,那么无处不在。 这样做一年,它已移动到你的指尖。

将字段设置为null会让您想到“做某事”,因为作为开发人员,我们总是担心“忘记某些事情”。

最后,我们有必须明确关闭的API,因为没有真正的语言支持来说“当我完成它时关闭它”并让计算机像GC一样解决它。 所以你有一个API,你必须在那里调用清理代码和API。 这会吸引和鼓励像上面这样的模式。

它可能来自VB,它使用引用计数策略进行内存管理和对象生存期。 将引用设置为Nothing (等效于null)将减少引用计数。 一旦该计数变为零,则该对象被同步销毁。 离开方法的范围后,计数将自动递减,因此即使在VB中,这种技术大多无用,但是在某些特殊情况下,您需要贪婪地销毁对象,如下面的代码所示。

 Public Sub Main() Dim big As Variant Set big = GetReallyBigObject() Call big.DoSomething Set big = Nothing Call TimeConsumingOperation Call ConsumeMoreMemory End Sub 

在上面的代码中, big引用的对象在没有调用Set big = Nothing情况下一直徘徊到最后。 如果该方法中的其他内容是耗时的操作或产生更多的内存压力,那么这可能是不合需要的。

它来自C / C ++,其中明确地将指针设置为null是常态(消除悬空指针 )

在调用free()之后:

 #include  { char *dp = malloc ( A_CONST ); // Now that we're freeing dp, it is a dangling pointer because it's pointing // to freed memory free ( dp ); // Set dp to NULL so it is no longer dangling dp = NULL; } 

经典的VB开发人员在编写COM组件时也做了同样的事情,以防止内存泄漏。

它在具有确定性垃圾收集和没有RAII的语言中更常见,例如旧的Visual Basic, 但即使在那里它也是不必要的,并且通常需要打破循环引用。 所以它可能真的源于那些在整个地方使用哑指针的坏C ++程序员。 在C ++中,删除它们之后将哑指针设置为0是有意义的,以防止双重删除。

我在VBScript代码(经典ASP)中看到了很多,我认为它来自那里。

我认为它曾经是前C / C ++开发人员常见的误解。 他们知道GC会释放他们的记忆,但他们并不真正了解何时以及如何。 干净吧继续:)

我怀疑这种模式来自于将C ++代码转换为C#而不会暂停理解C#finalization和C ++ finalization之间的差异。 在C ++中,我经常在析构函数中抛出一些东西,用于调试目的(这样你可以在调试器中看到引用不再有效),或者很少,因为我想要一个智能对象被释放。 (如果这意味着我宁愿在其上调用Release并使代码的含义对于维护者来说是清晰的。)正如你所注意到的,这在C#中毫无意义。

由于不同的原因,你也一直在VB / VBScript中看到这种模式。 我想了解一下这可能导致的原因:

http://blogs.msdn.com/b/ericlippert/archive/2004/04/28/122259.aspx

可能是由于foo是实例变量而不是局部变量的事实而分配null的约定,您应该在GC收集它之前删除引用。 有人在第一句话中睡觉并开始使所有变量无效; 人群紧随其后。

它来自C / C ++,对已经发布的指针执行free()/ delete操作可能会导致崩溃,而释放NULL指针却什么都不做。

这意味着此构造(C ++)将导致问题

 void foo() { myclass *mc = new myclass(); // lets assume you really need new here if (foo == bar) { delete mc; } delete mc; } 

虽然这会奏效

 void foo() { myclass *mc = new myclass(); // lets assume you really need new here if (foo == bar) { delete mc; mc = NULL; } delete mc; } 

结论:IT在C#,Java以及其他任何垃圾收集语言中都是完全不必要的。

考虑稍作修改:

 public void FooBar() { object foo = null; object bar = null; try { foo = new object(); bar = new object(); // Code which throws exception. } finally { // Destroying objects foo = null; bar = null; } vaboom(foo,bar); } 

作者可能想要确保伟大的Vaboom(*)如果先前抛出并捕获exception,则不会指向格式错误的对象。 导致防御性编码的偏执狂在这项业务中并不一定是坏事。

(*)如果你知道他是谁,你知道。

VB开发人员必须处理所有对象,以尝试减少内存泄漏的可能性。 我可以想象这是因为VB开发人员迁移到.NEt / c#

我可以看到它来自对垃圾收集如何工作的误解,或者是迫使GC立即启动的努力 – 也许是因为对象foobar非常大。

我之前在一些Java代码中已经看过这个。 它用于静态变量以表示应该销毁该对象。

它可能并非源自Java,因为将它用于除静态变量以外的任何东西在Java中也没有意义。

它来自C ++代码,尤其是智能指针 。 在这种情况下,它相当于C#中的.Dispose()

这不是一个好习惯,至多是开发者的本能。 通过在C#中分配null没有实际价值,除非可能帮助GC打破循环引用。