在许多不同的公司中使用各种.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
是否还有其他价值?
答案 0 :(得分:22)
这是 FUD cargo cult programming(感谢Daniel Earwicker)开发人员习惯于“释放”资源,错误的GC实现和糟糕的API。
有些GC没有很好地处理循环引用。要摆脱它们,你必须“在某个地方”打破这个循环。哪里?好吧,如果有疑问,那么无处不在。做了一年,它已经触手可及。
同样将字段设置为null
可让您了解“做某事”,因为作为开发人员,我们总是担心“忘记某些事情”。
最后,我们有必须明确关闭的API,因为没有真正的语言支持来说“当我完成它时关闭它”并让计算机像GC一样解决它。所以你有一个API,你必须在那里调用清理代码和API。这会吸引并鼓励上述模式。
答案 1 :(得分:8)
它可能来自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
的情况下会一直徘徊到最后。如果方法中的其他内容是耗时的操作或产生更多的内存压力,那么这可能是不合需要的。
答案 2 :(得分:3)
它来自C / C ++,其中明确地将指针设置为null是常态(消除dangling pointers)
调用free()之后:
#include <stdlib.h>
{
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组件时也做了同样的事情,以防止内存泄漏。
答案 3 :(得分:3)
在具有确定性垃圾收集且没有RAII的语言中更常见,例如旧的Visual Basic,,但即使在那里它也是不必要的,并且经常需要打破循环引用。所以它可能真的源于那些在整个地方使用哑指针的坏C ++程序员。在C ++中,删除它们后将哑指针设置为0是有意义的,以防止双重删除。
答案 4 :(得分:2)
我在VBScript代码(经典ASP)中看到了很多,我认为它来自那里。
答案 5 :(得分:2)
我认为它曾经是前C / C ++开发人员常见的误解。他们知道GC会释放他们的记忆,但他们并不真正了解何时以及如何。只需清理它并继续:)
答案 6 :(得分:2)
我怀疑这种模式来自于将C ++代码转换为C#而不会暂停理解C#finalization和C ++ finalization之间的差异。在C ++中,我经常在析构函数中抛出一些东西,用于调试目的(这样你可以在调试器中看到引用不再有效),或者很少,因为我想要一个智能对象被释放。 (如果这意味着我宁愿在其上调用Release 并使代码的含义对于维护者来说是清晰的。)正如你所注意到的,这在C#中毫无意义。
由于各种原因,您也一直在VB / VBScript中看到这种模式。我对这可能导致的原因有些疑惑:
http://blogs.msdn.com/b/ericlippert/archive/2004/04/28/122259.aspx
答案 7 :(得分:1)
它来自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以及其他任何垃圾收集语言中完全不必要。
答案 8 :(得分:1)
可能是由于foo
是实例变量而不是局部变量的事实而分配null的约定,您应该在GC收集它之前删除引用。有人在第一句话中睡觉并开始使所有变量无效;人群紧随其后。
答案 9 :(得分:1)
考虑稍作修改:
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(*)在以前抛出并捕获异常时没有指向格式错误的对象的指针。导致防御性编码的偏执狂在这项业务中并不一定是坏事。
(*)如果你知道他是谁,你知道。
答案 10 :(得分:0)
答案 11 :(得分:0)
我可以看到它来自对垃圾收集如何工作的误解,或者迫使GC立即启动的努力 - 可能是因为对象foo
和bar
非常大。
答案 12 :(得分:0)
之前我在一些Java代码中看到了这一点。它被用在一个静态变量上,表示该对象应该被销毁。
它可能并非源自Java,因为将它用于除静态变量以外的任何东西在Java中也没有意义。
答案 13 :(得分:-4)
它来自C ++代码,特别是智能指针。在这种情况下,它相当于C#中的.Dispose()
。
这不是一个好习惯,至多是开发者的本能。通过在C#中分配null
没有实际价值,除非可能帮助GC打破循环引用。