在 .NET 中使用后将对象设置为 Null/Nothing

Setting Objects to Null/Nothing after use in .NET

提问人:John 提问时间:8/6/2008 最后编辑:Chris CatignaniJohn 更新时间:5/18/2023 访问量:113185

问:

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

我知道在.NET中,必须释放实现接口的对象的任何实例以释放一些资源,尽管该对象在被释放后仍然可以是某种东西(因此是表单中的属性),所以我假设它仍然可以驻留在内存中或至少部分驻留在内存中?IDisposableisDisposed

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

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

MSDN 文章从不在示例中这样做,目前我这样做是因为我不能这样做 看到危害。但是,我遇到了各种意见,因此任何评论都是有用的。

C# .NET vb.net 内存管理

评论

4赞 Tim M. 9/27/2010
+1 好问题。有谁知道编译器将完全优化赋值的情况?即是否有人在不同情况下查看 MSIL,并注意到 IL 将对象设置为 null(或缺少 null)。
1赞 David Klempfner 12/3/2022
这是一篇Microsoft文章,其中包含将 var 设置为 null 的示例(请参阅文章末尾):learn.microsoft.com/en-us/training/modules/...

答:

-2赞 GateKiller 8/6/2008 #1

某些对象假定强制从内存中删除资源的方法。.dispose()

评论

12赞 Marc Gravell 11/2/2008
不,它没有;Dispose() 收集对象 - 它用于执行确定性清理,通常释放非托管资源。
1赞 nicodemus13 4/6/2011
请记住,确定性仅适用于托管资源,而不适用于非托管资源(即内存)
13赞 Karl Seguin 8/6/2008 #2

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

评论

1赞 user2323308 8/8/2014
关于共享链接中内存的漂亮而详细的解释
1赞 Imre Pühvel 3/27/2019
链接断开。如果没有链接的内容,这个答案是相当有用的,应该删除。
0赞 Junaid Pathan 10/8/2022
由于链接不起作用,现在是人们必须明白在答案中仅提供链接是没有用的的时候了。我们必须始终提供链接所提供内容的摘要,然后提供链接,只是为了配合我们所写的内容。
5赞 Bob 8/6/2008 #3

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

评论

2赞 Karl Seguin 8/6/2008
这是真的,但这也意味着你可能应该重构你的代码。我认为我不需要在其预期范围之外声明变量。
2赞 stakx - no longer contributing 5/28/2017
如果“变量”被理解为包括对象字段,那么这个答案很有意义。如果“变量”仅表示“局部变量”(方法的),那么我们在这里谈论的可能是利基案例(例如,运行时间跨度比平时长得多的方法)。
7赞 Steve T 8/6/2008 #4

也:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of
1赞 Patrick 8/6/2008 #5

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

但这种事情只在长寿的收藏中才有意义。如果队列在创建它的函数结束时无法存活,那么它就不那么重要了。

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

78赞 Kev 8/6/2008 #6

Karl 是绝对正确的,使用后无需将对象设置为 null。如果一个对象实现了,只需确保在完成该对象时调用(包装在 ..,或块中)。但是,即使您不记得调用,对象上的finaliser方法也应该为您调用。IDisposableIDisposable.Dispose()tryfinallyusing()Dispose()Dispose()

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

深入研究 IDisposable

和这个

了解 IDisposable

试图对 GC 及其管理策略进行二次猜测没有任何意义,因为它是自我调整和不透明的。Jeffrey Richter 在 Dot Net Rocks 上就内部工作原理进行了很好的讨论:Jeffrey Richter 在 Windows 内存模型和 Richters 的著作 CLR via C# 第 20 章有一个很好的处理:

评论

6赞 Scott Dorman 11/1/2008
关于不设置为 null 的规则并不是“硬性且快速”......如果对象被放在大型对象堆上(大小为 >85K),则在使用完对象后将对象设置为 null 将对 GC 有所帮助。
0赞 Kev 11/2/2008
我在有限的程度上同意,但除非你开始经历内存压力,否则我认为没有必要通过在使用后将对象设置为 null 来“过早优化”。
22赞 BobbyShaftoe 12/20/2008
“不要过早优化”的整个业务听起来更像是“更喜欢慢,不要担心,因为 CPU 越来越快,而 CRUD 应用程序无论如何都不需要速度。不过可能只是我。:)
21赞 BobRodes 6/8/2012
它的真正含义是“垃圾回收器比你更擅长管理内存。不过,这可能只是我。:)
4赞 Brent Rittenhouse 8/4/2017
@BobbyShaftoe:说“过早的优化总是不好的”可能是错误的,就像跳到“听起来更像是'喜欢慢'”的相反极端一样。没有一个理性的程序员会这么说。这是关于细微差别和聪明地优化什么。我个人会担心代码的清晰度,然后实际测试性能,因为我个人看到很多人(包括我年轻时的我自己)花了太多时间制作“完美”的算法,结果却让它在 100,000 次迭代中节省了 0.1 毫秒,而可读性却完全被拍摄了。
7赞 dbkk 8/9/2008 #7

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

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

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

最好在释放字段后将其清空,并在再次使用该字段的行处获得 NullPtrEx。否则,你可能会遇到一些神秘的错误(取决于DoSomething的确切功能)。

评论

8赞 nicodemus13 4/6/2011
好吧,如果已释放的对象已被释放,则应抛出 ObjectDisposedException。据我所知,这确实需要到处都是样板代码,但话又说回来,无论如何,Disposed 是一个经过深思熟虑的范式。
3赞 Suamere 1/12/2016
Ctrl+F 表示 。如果找到它,则说明您未正确使用 IDisposable。一次性物品的唯一用途应该是在使用块的范围内。在使用块之后,您甚至无法再访问。在 using 块中,不需要设置 to,using 块将为您处理对象。.Dispose()myFieldnull
1赞 Flydog57 7/22/2022
@nicodemus13:没有。允许多次调用。只有在无法做到这一点的情况下(操作所需的某些资源不再存在),对象才应该抛出。stackoverflow.com/questions/8923853/......DisposeDispose
7赞 mbillard 8/9/2008 #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

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

评论

0赞 Suamere 1/12/2016
我尝试过使用自定义本地范围(主要是 smarta$$)。公司爆炸了。
0赞 Suamere 1/12/2016
另一方面:这是因为 c# 编译器将查找实现 IDisposable 的局部范围变量,并将调用 .当其范围结束时(大多数时间)进行处置。然而。。。SQL 连接是 .Dispose() 永远不会优化。有些类型需要明确关注,所以我个人总是明确地做事,以免被咬伤。
38赞 Wilka 8/15/2008 #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'd,但

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 通常会优化对 null 的赋值,因此两段代码最终是相同的。

评论

0赞 Guru Josh 5/7/2014
这是一个有趣的观点。我一直认为,对象在完成作用域的方法之前不会超出作用域。当然,除非该对象的范围限定在 Using 块内,或者显式设置为 Nothing 或 null。
2赞 NotMe 7/10/2014
确保它们保持活力的首选方法是使用 See ericlippert.com/2013/06/10/construction-destructionGC.KeepAlive(someType);
0赞 AvahW 6/6/2023
对于想要明确答案的任何其他人来说,变量一旦停止使用就有资格进行垃圾回收,无论它是否仍在“范围内”,因此显式设置为 null 是没有意义的。有关更多详细信息,请参阅此问题:stackoverflow.com/questions/17497923
1赞 Scott Dorman 8/17/2008 #10

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

在大多数情况下,将对象设置为 null 不起作用。只有在使用“大对象”时,才应确保这样做,该对象的大小大于 84K(例如位图)。

3赞 KenF 4/11/2012 #11

这种“使用后无需将对象设置为 null”并不完全准确。有时,您需要在释放变量后将其清空。

是的,完成后,您应该始终致电或拨打任何有电话的人。无论是文件句柄、数据库连接还是一次性对象。.Dispose().Close()

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

假设我有并实例化了 . 具有一个名为 的公共属性。ObjAclass AClass APropBclass B

在内部,使用 的私有变量 ,默认为 null。使用时,它会检查是否为 null,如果是,则打开实例化为 .然后返回 .PropB_BPropB.Get()_PropBB_PropB_PropB

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

需要 null 的地方是,如果您以某种方式重置或更改 A,则 的内容是 的先前值的子项,您将需要 Dispose AND null out,以便 LazyLoad 可以重置以获取正确的值(如果代码需要它)。_PropBA_PropB

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

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

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

正如 dbkk 所暗示的那样,核心原因是父容器 ( with ) 将 的实例保持在范围内,尽管 .ObjAPropB_PropBDispose()

评论

0赞 rollsch 7/17/2017
一个很好的例子,说明手动设置为 null 对调用方来说意味着更致命的错误,这是一件好事。
6赞 Munish Goyal 4/17/2012 #12

通常不需要设置为 null。但是,假设您的类中有一个 Reset 功能。

然后,您可能会这样做,因为您不想调用 dispose 两次,因为某些 Dispose 可能无法正确实现并引发 System.ObjectDisposed 异常。

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

评论

0赞 Thulani Chivandikwa 9/13/2019
最好用一个单独的标志来跟踪它。
0赞 John 5/10/2016 #13

我相信根据 GC 实现者的设计,你不能通过无效来加速 GC。我敢肯定,他们希望你不要担心 GC 如何/何时运行——把它当作这个无处不在的存在来保护和监视你......(低头,向天空举起拳头)......

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

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

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

我是这样看的: 编程语言的存在是为了让人们给其他人一个意图的想法,让编译器向编译器发出一个关于该做什么的工作请求——编译器将该请求转换为一种不同的语言(有时是几种)——CPU 可以给出你使用的语言、你的选项卡设置、 注释、风格重点、变量名称等 -- CPU 完全与位流有关,它告诉它要拨弄哪些寄存器和操作码以及内存位置。用代码编写的许多内容不会按照我们指定的顺序转换为 CPU 消耗的内容。我们的C,C++,C#,Lisp,Babel,汇编器或任何理论而不是现实,都写成工作陈述。你所看到的不是你得到的,是的,即使是在汇编语言中。

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

任何规则都有例外。在易失性内存、静态内存、竞争条件、单例、使用“过时”数据以及所有这些腐烂的场景中,情况就不同了:你需要管理自己的内存,锁定和无效,因为内存不是 GC'd Universe 的一部分——希望每个人都能理解这一点。其余时间,对于 GC 语言来说,这是一个风格问题,而不是必要性或保证性能提升。

归根结底,请确保您了解哪些符合 GC 条件,哪些不符合条件;适当地锁定、处置和作废;打蜡,打蜡;吸气,呼气;对于其他一切,我说:如果感觉良好,就去做。您的里程可能会有所不同......理应如此......

3赞 Ebleme 3/27/2019 #14

Stephen Cleary 在这篇文章中解释得很好:我应该将变量设置为 null 以帮助垃圾回收吗?

说:

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

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

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

我们应该考虑的重要事情是静态场

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

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

结论:

静态场;仅此而已。其他任何事情都是浪费时间

0赞 Thulani Chivandikwa 9/13/2019 #15

我认为将某些东西设置回 null 是很混乱的。想象一下这样一种情况,即设置为现在的项通过属性公开。现在不知何故,某些代码段意外地使用此属性,在项目被释放后,您将得到一个空引用异常,这需要一些调查才能弄清楚到底发生了什么。

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

0赞 Nilakantheswar Patnaik 5/18/2023 #16

根据我近乎实时的系统设计经验(1 秒轮询 100 米的电力),我们实现了更好的性能,当不再需要对象时,使对象为空。将工作留给垃圾回收器 (GC) 会使其变慢,因为内存在进程的 RAM 内存使用量中逐渐增加,例如 2-3 小时的 1 秒数据轮询。内存没有减少的趋势,而是有增加的趋势。一个,我们在不需要它们后使许多这样的对象成为空,我们说几乎是内存的稳定,我们没有尝试的内存的完全稳定,因为我们有我们不想更改的第三方库。对于在连续运行几天内重新启动服务的应用程序,我们稳定了平衡内存处置延迟。据我了解,GS进程在Windows操作系统中的优先级很低,也许专家可以纠正我。我觉得当高性能应用程序中不再需要对象时,我们将对象设为 null,因此需要的峰值 RAM 更少。 wiki 参考资料在缺点部分在这方面写得很好,其中给出的 Apple 参考资料也很好,为什么在性能方面使 null 对内存密集型和时间关键型应用程序有利。Wiki 在 https://en.wikipedia.org/wiki/Garbage_collection_(computer_science) 中提到,“缺点 GC 使用计算资源来决定要释放的内存。因此,为了方便起见,不在源代码中手动注释对象生存期的代价是开销,这可能会损害程序性能。2005 年的一篇同行评议论文得出结论,GC 需要五倍的内存来补偿这种开销,并使用理想化的显式内存管理来执行与同一程序一样快的运行速度。但是,与使用预言机插入释放调用生成的程序进行比较,该程序是通过从分析器下运行的程序中收集跟踪来实现的,并且该程序仅对程序的一次特定执行是正确的。与内存层次结构效应的交互可能会使这种开销在难以预测或在常规测试中检测的情况下无法忍受。苹果公司给出了对性能的影响,这是不在iOS中采用垃圾回收的一个原因,尽管它是最需要的功能。 Apple 为什么 iOS OS 没有给出 GC 的参考也很有用,并且仍然将 GC 实现保留在 Apple 在 2011 年 3 月发布的 XCode 开发人员工具启动 ppt(幻灯片 61-66)中的一张幻灯片中;可能更高版本可以证实这一事实(请参阅本 Wiki 的 [9] 参考(使用 Apple 用户/密码访问 ppt)https://developer.apple.com/devcenter/download.action?path=/wwdc_2011/adc_on_itunes__wwdc11_sessions__pdf/300developer_tools_kickoff.pdf)。