【问题标题】:Setting Objects to Null/Nothing after use in .NET在 .NET 中使用后将对象设置为 Null/Nothing
【发布时间】:2010-09-05 09:23:31
【问题描述】:

完成后,是否应该将所有对象设置为null(VB.NET 中的Nothing)?

我了解在 .NET 中,必须释放实现 IDisposable 接口的任何对象实例以释放一些资源,尽管对象在释放后仍然可以是某物(因此表单中的 isDisposed 属性),所以我认为它仍然可以驻留在内存中或至少部分驻留在内存中?

我也知道,当一个对象超出范围时,它会被标记为为垃圾收集器的下一次传递做好准备(尽管这可能需要一些时间)。

因此,考虑到这一点,将其设置为 null 会加快系统释放内存的速度,因为它不必确定它不再在范围内,它们是否有任何不良副作用?

MSDN 文章在示例中从不这样做,目前我这样做是因为我做不到 见害处。但是我遇到了各种各样的意见,所以任何 cmet 都是有用的。

【问题讨论】:

  • +1 好问题。有谁知道编译器会完全优化分配的情况?也就是说,有没有人在不同的情况下查看过 MSIL,并注意到 IL 将对象设置为 null(或缺少)。

标签: c# .net vb.net memory-management null


【解决方案1】:

一些对象假设.dispose() 方法强制将资源从内存中删除。

【讨论】:

  • 不,它没有; Dispose() 收集对象 - 它用于执行确定性清理,通常释放非托管资源。
  • 请记住,确定性仅适用于托管资源,而不适用于非托管资源(即内存)
【解决方案2】:

不,不要为空对象。您可以查看https://web.archive.org/web/20160325050833/http://codebetter.com/karlseguin/2008/04/28/foundations-of-programming-pt-7-back-to-basics-memory/ 了解更多信息,但将内容设置为 null 不会做任何事情,除了弄脏您的代码。

【讨论】:

  • 共享链接中关于内存的详细解释
  • 链接已损坏。没有链接内容,这个答案比较没用,应该删除。
【解决方案3】:

您应该将变量设置为 null 的唯一情况是当变量没有超出范围并且您不再需要与其关联的数据时。否则就没有必要了。

【讨论】:

  • 没错,但这也意味着您可能应该重构您的代码。我认为我不需要在预期范围之外声明变量。
  • 如果“变量”被理解为包括对象字段,那么这个答案很有意义。在“变量”仅表示(方法的)“局部变量”的情况下,那么我们可能在这里谈论的是小众案例(例如,运行时间比平时长得多的方法)。跨度>
【解决方案4】:

还有:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

【讨论】:

    【解决方案5】:

    在某些情况下,空引用是有意义的。例如,当您编写一个集合(如优先级队列)时,根据您的合同,在客户端将这些对象从队列中删除后,您不应该为客户端保留这些对象。

    但这种事情只在长期收藏中很重要。如果队列无法在创建它的函数结束后继续存在,那么它的重要性就小得多了。

    总的来说,你真的不应该打扰。让编译器和 GC 完成他们的工作,这样您就可以完成您的工作。

    【讨论】:

      【解决方案6】:

      Karl 是绝对正确的,使用后不需要将对象设置为 null。如果一个对象实现了IDisposable,只需确保在完成该对象后调用IDisposable.Dispose()(包装在try..finallyusing() 块中)。但即使你不记得调用Dispose(),对象上的终结器方法也应该为你调用Dispose()

      我认为这是一种很好的治疗方法:

      Digging into IDisposable

      还有这个

      Understanding IDisposable

      尝试重新猜测 GC 及其管理策略没有任何意义,因为它是自我调整且不透明的。在 Dot Net Rocks 上与 Jeffrey Richter 就内部运作进行了很好的讨论:Jeffrey Richter on the Windows Memory Model 和 里氏书CLR via C#第20章有很好的处理:

      【讨论】:

      • 关于不设置为 null 的规则不是“硬而快”...如果对象被放在大对象堆上(大小>85K),如果你设置它会帮助 GC使用完毕后对象为 null。
      • 我在一定程度上同意,但除非您开始遇到内存压力,否则我认为没有必要通过在使用后将对象设置为 null 来“过早优化”。
      • 这整个“不要过早优化”的业务听起来更像是“宁愿慢也不要担心,因为 CPU 越来越快,而 CRUD 应用程序也不需要速度。”可能只是我。 :)
      • 真正的意思是“垃圾收集器比你更擅长管理内存。”不过,那可能只是我。 :)
      • @BobbyShaftoe:说“过早的优化总是不好”可能是错误的,因为它跳到了“听起来更像是'喜欢慢'”的相反极端。也没有一个理性的程序员会说。这是关于细微差别和对优化内容的明智之举。我个人会担心代码的清晰度,然后实际测试性能,因为我个人看到很多人(包括我年轻时的我)花了太多时间来制作“完美”的算法,只是为了节省 0.1 毫秒在 100,000 次迭代中,可读性完全被击中。
      【解决方案7】:

      一般来说,使用后不需要为空对象,但在某些情况下,我发现这是一个很好的做法。

      如果一个对象实现了 IDisposable 并存储在一个字段中,我认为最好将其设为 null,只是为了避免使用已处置的对象。以下类型的错误可能会很痛苦:

      this.myField.Dispose();
      // ... at some later time
      this.myField.DoSomething();
      

      最好在处理完字段后将其清空,并在再次使用该字段的行处获得 NullPtrEx。否则,您可能会遇到一些神秘的错误(具体取决于 DoSomething 的作用)。

      【讨论】:

      • 好吧,如果已处置的对象已被处置,则应抛出 ObjectDisposedException。据我所知,这确实需要到处都是样板代码,但话又说回来,Disposed 无论如何都是一个经过深思熟虑的范例。
      • Ctrl+F 表示.Dispose()。如果您找到它,则说明您没有正确使用 IDisposable。一次性对象的唯一用途应该是在 using-block 的范围内。在 using-block 之后,您甚至无法再访问myField。在 using 块中,不需要设置为 null,using-block 会为您处理对象。
      【解决方案8】:

      如果您觉得需要null 变量,则可能是您的代码结构不够紧凑。

      有多种方法可以限制变量的范围:

      正如 Steve Tranby

      所述
      using(SomeObject object = new SomeObject()) 
      {
        // do stuff with the object
      }
      // the object will be disposed of
      

      同样,您可以简单地使用大括号:

      {
          // Declare the variable and use it
          SomeObject object = new SomeObject()
      }
      // The variable is no longer available
      

      我发现使用不带任何“标题”的大括号可以真正清理代码并使其更易于理解。

      【讨论】:

      • 我曾尝试使用自定义本地范围(主要是 smarta$$)。公司爆炸了。
      • 另外注意:这是因为 c# 编译器会找到实现 IDisposable 的局部作用域变量,并在其作用域结束时调用 .Dispose(大部分时间)。但是... SQL 连接是 .Dispose() 从未优化过的重要时刻。有些类型需要明确注意,所以我个人总是明确地做事,以免被咬。
      【解决方案9】:

      当您完成对象时避免将对象设置为 null 的另一个原因是,它实际上可以使它们保持更长时间的活动。

      例如

      void foo()
      {
          var someType = new SomeType();
          someType.DoSomething();
          // someType is now eligible for garbage collection         
      
          // ... rest of method not using 'someType' ...
      }
      

      将允许 someType 引用的对象在调用“DoSomething”之后进行 GC,但是

      void foo()
      {
          var someType = new SomeType();
          someType.DoSomething();
          // someType is NOT eligible for garbage collection yet
          // because that variable is used at the end of the method         
      
          // ... rest of method not using 'someType' ...
          someType = null;
      }
      

      有时可能会使对象保持活动状态,直到方法结束。 JIT will usually optimized away the assignment to null,所以两段代码最终都是一样的。

      【讨论】:

      • 这是一个有趣的观点。我一直认为对象不会超出范围,直到它们的作用域方法完成之后。当然,除非对象的范围在 Using 块内,或者明确设置为 Nothing 或 null。
      • 确保它们存活的首选方法是使用GC.KeepAlive(someType); 请参阅ericlippert.com/2013/06/10/construction-destruction
      【解决方案10】:

      也可以看看这篇文章:http://www.codeproject.com/KB/cs/idisposable.aspx

      在大多数情况下,将对象设置为 null 无效。唯一应该确保这样做的情况是,如果您使用的是大于 84K 的“大对象”(例如位图)。

      【讨论】:

        【解决方案11】:

        这种“使用后无需将对象设置为null”并不完全准确。有时您需要在处理后将变量设为 NULL。

        是的,当你完成后,你应该总是打电话给.Dispose().Close()。无论是文件句柄、数据库连接还是一次性对象。

        与之不同的是非常实用的LazyLoad模式。

        假设我已经实例化了ObjAclass AClass A 有一个名为 PropB 的公共属性,属于 class B

        在内部,PropB 使用 _B 的私有变量,默认为 null。当使用PropB.Get() 时,它会检查_PropB 是否为空,如果是,则打开将B 实例化为_PropB 所需的资源。然后它返回_PropB

        根据我的经验,这是一个非常有用的技巧。

        需要 null 的地方是,如果您以某种方式重置或更改 A,使 _PropB 的内容是 A 先前值的子项,您将需要 Dispose 和 null out _PropB因此,如果代码需要,LazyLoad 可以重置以获取正确的值。

        如果您只执行_PropB.Dispose() 并在不久之后期望 LazyLoad 的 null 检查成功,则它不会为 null,并且您将看到陈旧的数据。实际上,您必须在 Dispose() 之后将其设为空才能确定。

        我当然希望不是这样,但是我现在有代码在 _PropB 上的 Dispose() 之后以及在执行 Dispose 的调用函数之外(因此几乎超出范围)表现出这种行为,私有属性仍然不为空,并且陈旧的数据仍然存在。

        最终,已处置的属性将无效,但从我的角度来看,这是不确定的。

        正如 dbkk 所暗示的那样,核心原因是父容器(ObjAPropB)将_PropB 的实例保留在范围内,尽管Dispose()

        【讨论】:

        • 很好的例子展示了手动设置为 null 对调用者来说意味着一个更致命的错误,这是一件好事。
        【解决方案12】:

        一般不需要设置为null。但是假设您的班级中有重置功能。

        那么你可能会这样做,因为你不想调用 dispose 两次,因为某些 Dispose 可能没有正确实现并抛出 System.ObjectDisposed 异常。

        private void Reset()
        {
            if(_dataset != null)
            {
               _dataset.Dispose();
               _dataset = null;
            }
            //..More such member variables like oracle connection etc. _oraConnection
         }
        

        【讨论】:

        • 最好只用一个单独的标志来跟踪它。
        【解决方案13】:

        我相信根据 GC 实现者的设计,您不能 加速 GC 无效。我敢肯定,他们不希望您担心 GC 的运行方式/时间 - 将其视为无处不在的 Being 为您保护和守护...(低头,举起拳头向天)...

        就个人而言,当我将变量作为一种自我文档的形式完成时,我经常将它们显式设置为 null。我不会声明、使用,然后再设置为 null ——我在不再需要它们后立即为 null。我明确地说,“我和你正式结束了......走开......”

        在 GC'd 语言中是否有必要进行无效化?不,它对 GC 有帮助吗?也许是,也许不是,不确定,按设计我真的无法控制它,不管今天这个版本或那个版本的答案,未来的 GC 实现可能会改变我无法控制的答案。另外,如果/当清空被优化时,它只不过是一个花哨的comment,如果你愿意的话。

        我想如果它能让下一个追随我的可怜的傻瓜更清楚我的意图,并且如果它“可能”有时可能对 GC 有所帮助,那么这对我来说是值得的。主要是它让我感觉整洁清晰,而Mongo喜欢感觉整洁和清晰。 :)

        我是这样看的:编程语言的存在是为了让人们给其他人一个意图的想法,让编译器对要做什么的工作请求——编译器将该请求转换为不同的语言(有时是几种) CPU——CPU 可以提示你使用的语言、选项卡设置、cmets、风格重点、变量名称等——CPU 的全部内容是比特流,它告诉它哪些寄存器、操作码和内存位置玩弄。许多用代码编写的东西并没有按照我们指定的顺序转换成 CPU 消耗的东西。我们的 C、C++、C#、Lisp、Babel、汇编程序或任何理论而不是现实,写成工作声明。你看到的不是你得到的,是的,即使是汇编语言。

        我确实理解“不必要的东西”(如空白行)“只不过是杂乱无章的代码”的心态。那是我职业生涯的早期;我完全明白。在这个关头,我倾向于使代码更清晰的东西。这不像是我在我的程序中添加了 50 行“噪音”——这里或那里只有几行。

        任何规则都有例外。在具有易失性内存、静态内存、竞争条件、单例、“陈旧”数据的使用以及所有此类腐烂的情况下,情况不同:您需要管理自己的内存,适当地锁定和取消,因为内存不是GC'd Universe——希望每个人都明白这一点。其余时间使用 GC 语言,这是风格问题,而不是必要性或有保证的性能提升。

        最后,请确保您了解哪些符合 GC 资格,哪些不符合;适当地锁定、处置和取消;上蜡,脱蜡;吸气,呼气;对于我说的其他一切:如果感觉很好,那就去做。您的里程可能会有所不同......应该......

        【讨论】:

          【解决方案14】:

          Stephen Cleary 在这篇文章中解释得很好:Should I Set Variables to Null to Assist Garbage Collection?

          说:

          简短的回答,对于不耐烦的人 是的,如果变量是静态字段,或者您正在编写可枚举方法(使用 yield return)或异步方法(使用 async 和 await)。否则,不行。

          这意味着在常规方法(不可枚举和非异步)中,不要将局部变量、方法参数或实例字段设置为 null。

          (即使你正在实现 IDisposable.Dispose,你仍然不应该将变量设置为 null)。

          我们应该考虑的重要一点是静态字段

          静态字段始终是根对象,因此垃圾收集器始终认为它们是“活动的”。如果静态字段引用了不再需要的对象,则应将其设置为 null,以便垃圾收集器将其视为符合收集条件。

          如果整个进程正在关闭,将静态字段设置为 null 是没有意义的。届时整个堆都将被垃圾回收,包括所有根对象。

          结论:

          静态字段;就是这样。其他任何事情都是浪费时间

          【讨论】:

            【解决方案15】:

            我认为将某些内容设置回 null 很麻烦。想象一个场景,现在设置的项目通过属性公开。现在不知何故,在处理项目后,某些代码意外地使用了此属性,您将得到一个空引用异常,这需要进行一些调查才能弄清楚到底发生了什么。

            我相信框架一次性将允许抛出更有意义的 ObjectDisposedException。出于这个原因,不将这些设置回 null 会更好。

            【讨论】:

              猜你喜欢
              • 2010-10-03
              • 2012-04-18
              • 1970-01-01
              • 1970-01-01
              • 2016-07-28
              • 2012-10-18
              • 2010-10-09
              • 1970-01-01
              相关资源
              最近更新 更多