22

在许多不同公司中使用不同版本的 .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我没有意识到的还有一些额外的价值吗?

4

14 回答 14

22

这是由习惯于“释放”资源、糟糕的 GC 实现和糟糕的 API 的开发人员进行的FUD货物崇拜编程(感谢Daniel Earwicker )。

一些 GC 不能很好地处理循环引用。要摆脱它们,您必须在“某处”打破循环。在哪里?好吧,如果有疑问,那么无处不在。这样做一年,它就会进入你的指尖。

还将字段设置为null给您“做某事”的想法,因为作为开发人员,我们总是害怕“忘记某事”。

最后,我们有必须明确关闭的 API,因为没有真正的语言支持说“当我完成它时关闭它”并让计算机像使用 GC 一样计算出来。所以你有一个 API,你必须调用清理代码和你不需要的 API。这很糟糕并鼓励了上述模式。

于 2010-06-28T12:25:57.203 回答
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. 如果方法中的其他内容是耗时的操作或产生更多的内存压力,那可能是不可取的。

于 2010-06-28T13:04:57.213 回答
3

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

调用 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 组件以防止内存泄漏时也做了同样的事情。

于 2010-06-28T12:21:37.243 回答
3

它在具有确定性垃圾收集且没有 RAII 的语言中更为常见,例如旧的 Visual Basic,但即使在那里也没有必要,而且经常需要打破循环引用。所以它可能真的源于糟糕的 C++ 程序员,他们到处使用哑指针。在 C++ 中,删除后将哑指针设置为 0 以防止重复删除是有意义的。

于 2010-06-28T12:23:44.570 回答
2

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

于 2010-06-28T12:20:32.533 回答
2

我认为这曾经是前 C/C++ 开发人员之间普遍存在的误解。他们知道 GC 会释放他们的内存,但他们并不真正了解何时以及如何。只需清洁它并继续:)

于 2010-06-28T12:21:04.967 回答
2

我怀疑这种模式来自于将 C++ 代码翻译成 C# 而没有停下来了解 C# 终结和 C++ 终结之间的区别。在 C++ 中,我经常将析构函数中的内容清空,或者出于调试目的(以便您可以在调试器中看到引用不再有效),或者很少因为我想要释放智能对象。(如果这是我宁愿调用 Release的意思,并使代码的含义对维护者来说一目了然。)正如您所注意到的,这在 C# 中几乎没有意义。

由于不同的原因,您也一直在 VB/VBScript 中看到这种模式。我在这里思考了一下可能导致这种情况的原因:

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

于 2010-06-28T15:26:33.453 回答
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;
}

结论:在 C#、Java 和几乎任何其他垃圾收集语言中完全不需要 IT。

于 2010-06-28T12:21:53.280 回答
1

可能是分配 null 的约定源于foo实例变量而不是局部变量的事实,您应该在 GC 收集它之前删除引用。有人在第一句话的时候就睡着了,开始取消他们所有的变量;人群紧随其后。

于 2010-06-28T12:23:57.163 回答
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; 
    } 
    vavoom(foo,bar);
} 

如果之前抛出并捕获了异常,作者可能希望确保出色的 Vavoom (*) 不会获得指向错误对象的指针。偏执狂,导致防御性编码,在这个行业不一定是坏事。

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

于 2010-06-28T12:30:47.410 回答
0

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

于 2010-06-28T12:23:27.133 回答
0

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

于 2010-06-28T12:24:15.370 回答
0

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

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

于 2010-06-28T17:26:03.263 回答
-4

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

这不是一个好的做法,最多是开发人员的本能。在 C# 中分配没有真正的价值null,除了可能帮助 GC 打破循环引用。

于 2010-06-28T12:26:10.820 回答