提问人:John 提问时间:8/6/2008 最后编辑:Chris CatignaniJohn 更新时间:5/18/2023 访问量:113185
在 .NET 中使用后将对象设置为 Null/Nothing
Setting Objects to Null/Nothing after use in .NET
问:
完成所有对象后,是否应该将所有对象设置为(VB.NET)?null
Nothing
我知道在.NET中,必须释放实现接口的对象的任何实例以释放一些资源,尽管该对象在被释放后仍然可以是某种东西(因此是表单中的属性),所以我假设它仍然可以驻留在内存中或至少部分驻留在内存中?IDisposable
isDisposed
我还知道,当一个对象超出范围时,它会被标记为要收集,为垃圾收集器的下一次传递做好准备(尽管这可能需要一些时间)。
因此,考虑到这一点,是否会将其设置为加快系统释放内存的速度,因为它不必计算出它不再在范围内,它们是否有任何不良副作用?null
MSDN 文章从不在示例中这样做,目前我这样做是因为我不能这样做 看到危害。但是,我遇到了各种意见,因此任何评论都是有用的。
答:
某些对象假定强制从内存中删除资源的方法。.dispose()
评论
否,不要为空对象。您可以查看 https://web.archive.org/web/20160325050833/http://codebetter.com/karlseguin/2008/04/28/foundations-of-programming-pt-7-back-to-basics-memory/ 以获取更多信息,但将 things 设置为 null 不会执行任何操作,除了弄脏您的代码。
评论
唯一应将变量设置为 null 的情况是,当变量未超出范围并且不再需要与其关联的数据时。否则就没有必要了。
评论
也:
using(SomeObject object = new SomeObject())
{
// do stuff with the object
}
// the object will be disposed of
在某些情况下,空引用是有意义的。例如,当你编写一个集合(如优先级队列)时,根据你的协定,在客户端从队列中删除这些对象后,你不应该为客户端保留这些对象。
但这种事情只在长寿的收藏中才有意义。如果队列在创建它的函数结束时无法存活,那么它就不那么重要了。
总的来说,你真的不应该打扰。让编译器和 GC 完成它们的工作,这样你就可以完成你的工作。
Karl 是绝对正确的,使用后无需将对象设置为 null。如果一个对象实现了,只需确保在完成该对象时调用(包装在 ..,或块中)。但是,即使您不记得调用,对象上的finaliser方法也应该为您调用。IDisposable
IDisposable.Dispose()
try
finally
using()
Dispose()
Dispose()
我认为这是一个很好的治疗方法:
和这个
试图对 GC 及其管理策略进行二次猜测没有任何意义,因为它是自我调整和不透明的。Jeffrey Richter 在 Dot Net Rocks 上就内部工作原理进行了很好的讨论:Jeffrey Richter 在 Windows 内存模型和 Richters 的著作 CLR via C# 第 20 章有一个很好的处理:
评论
一般来说,使用后没有必要为空对象,但在某些情况下,我发现这是一个很好的做法。
如果一个对象实现了 IDisposable 并存储在字段中,我认为最好将其清空,只是为了避免使用已释放的对象。以下类型的错误可能会很痛苦:
this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();
最好在释放字段后将其清空,并在再次使用该字段的行处获得 NullPtrEx。否则,你可能会遇到一些神秘的错误(取决于DoSomething的确切功能)。
评论
.Dispose()
myField
null
Dispose
Dispose
如果您觉得需要 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
我发现使用没有任何“标题”的大括号可以真正清理代码并有助于使其更易于理解。
评论
在完成对象处理后避免将对象设置为 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 的赋值,因此两段代码最终是相同的。
评论
GC.KeepAlive(someType);
也看看这篇文章: http://www.codeproject.com/KB/cs/idisposable.aspx
在大多数情况下,将对象设置为 null 不起作用。只有在使用“大对象”时,才应确保这样做,该对象的大小大于 84K(例如位图)。
这种“使用后无需将对象设置为 null”并不完全准确。有时,您需要在释放变量后将其清空。
是的,完成后,您应该始终致电或拨打任何有电话的人。无论是文件句柄、数据库连接还是一次性对象。.Dispose()
.Close()
与此不同的是 LazyLoad 非常实用的模式。
假设我有并实例化了 . 具有一个名为 的公共属性。ObjA
class A
Class A
PropB
class B
在内部,使用 的私有变量 ,默认为 null。使用时,它会检查是否为 null,如果是,则打开实例化为 .然后返回 .PropB
_B
PropB.Get()
_PropB
B
_PropB
_PropB
根据我的经验,这是一个非常有用的技巧。
需要 null 的地方是,如果您以某种方式重置或更改 A,则 的内容是 的先前值的子项,您将需要 Dispose AND null out,以便 LazyLoad 可以重置以获取正确的值(如果代码需要它)。_PropB
A
_PropB
如果您只执行,并且不久之后期望 LazyLoad 的 null 检查成功,则它不会为 null,并且您将查看过时的数据。实际上,您必须在之后将其清空才能确定。_PropB.Dispose()
Dispose()
我当然希望不是这样,但我现在的代码在执行 Dispose 的调用函数(因此几乎超出范围)之后表现出这种行为,私有道具仍然不是 null,并且陈旧数据仍然存在。Dispose()
_PropB
最终,已处置的属性将无效,但从我的角度来看,这是不确定的。
正如 dbkk 所暗示的那样,核心原因是父容器 ( with ) 将 的实例保持在范围内,尽管 .ObjA
PropB
_PropB
Dispose()
评论
通常不需要设置为 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
}
评论
我相信根据 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 条件,哪些不符合条件;适当地锁定、处置和作废;打蜡,打蜡;吸气,呼气;对于其他一切,我说:如果感觉良好,就去做。您的里程可能会有所不同......理应如此......
Stephen Cleary 在这篇文章中解释得很好:我应该将变量设置为 null 以帮助垃圾回收吗?
说:
简短的回答,给不耐烦的人 是的,如果变量是静态字段,或者您正在编写可枚举方法(使用 yield return)或异步方法(使用 async 和 await)。否则,不可以。
这意味着,在常规方法(不可枚举和非异步)中,不要将局部变量、方法参数或实例字段设置为 null。
(即使你正在实现 IDisposable.Dispose,你仍不应将变量设置为 null)。
我们应该考虑的重要事情是静态场。
静态字段始终是根对象,因此垃圾回收器始终将它们视为“活动”。如果静态字段引用了不再需要的对象,则应将其设置为 null,以便垃圾回收器将其视为符合回收条件。
如果整个进程正在关闭,则将静态字段设置为 null 毫无意义。此时,整个堆将被垃圾回收,包括所有根对象。
结论:
静态场;仅此而已。其他任何事情都是浪费时间。
我认为将某些东西设置回 null 是很混乱的。想象一下这样一种情况,即设置为现在的项通过属性公开。现在不知何故,某些代码段意外地使用此属性,在项目被释放后,您将得到一个空引用异常,这需要一些调查才能弄清楚到底发生了什么。
我相信框架一次性用品将允许抛出更有意义的 ObjectDisposedException。出于这个原因,不将它们设置回 null 会更好。
根据我近乎实时的系统设计经验(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)。
评论