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

时间:2010-06-28 12:19:06

标签: c# .net design-patterns anti-patterns

在许多不同的公司中使用各种.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是否还有其他价值?

14 个答案:

答案 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)

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

时,这就是它的来源

答案 11 :(得分:0)

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

答案 12 :(得分:0)

之前我在一些Java代码中看到了这一点。它被用在一个静态变量上,表示该对象应该被销毁。

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

答案 13 :(得分:-4)

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

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