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


187

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

我知道,在.NET中,处置实现IDisposable接口以释放一些资源的对象的任何实例都是至关重要的,尽管该对象在处置后仍然可以是某种东西(因此isDisposed表单中的属性),因此我认为它仍然可以驻留在内存中还是至少有一部分?

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

因此,考虑到这一点,将对其进行设置以null加快系统释放内存的速度,因为不必确定它已不在范围之内,并且它们是否会产生不良影响?

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


4
+1个好问题。有人知道编译器将完全优化分配的情况吗?即是否有人在不同情况下查看了MSIL并指出IL将对象设置为null(或缺少null)。
Tim Medora 2010年

Answers:


73

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

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

挖掘IDisposable

还有这个

了解IDisposable

试图再次猜测GC及其管理策略没有任何意义,因为它是自调整的且不透明。在此处,关于Dot Net Rocks的Jeffrey Richter的内部工作方式进行了很好的讨论:Windows Memory Model和Richters的Jeffrey Richter 通过C#第20章对CLR进行了很好的论述:


6
不设置为null的规则不是“一成不变的” ...如果将对象放到大对象堆(大小大于85K)上,则在完成后将对象设置为null会帮助GC。使用它。
Scott Dorman

我在一定程度上同意,但是除非您开始遇到内存压力,否则我认为不需要在使用后将对象设置为null来“过早优化”。
凯夫(Kev)

21
“不要过早优化”这整个业务听起来更像是“慢一点,不要担心,因为CPU越来越快,CRUD应用程序也不需要速度。” 不过可能只是我。:)
BobbyShaftoe

19
真正的意思是“垃圾收集器比您更擅长管理内存。” 那可能只是我。:)
BobRodes 2012年

2
@BobbyShaftoe:说“过早的优化总是很糟糕,总是这样”可能是错误的,因为跳到“听起来更像是“喜欢慢”的声音”的相反极端。没有一个合理的程序员会说。这是关于细微差别,并对您的优化保持精明。我个人担心代码的清晰度和然后实际测试的性能,因为我亲眼看到很多人(包括我年轻的时候)花了很多时间来制作“完美”算法,却只节省了0.1ms在十万次迭代中,可读性被完全拍摄。
布伦特·里滕豪斯

36

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

例如

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

将允许在调用“ DoSomething”之后将someType引用的对象进行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通常会优化掉分配给空,所以代码端的向上是相同的两个位。


这是一个有趣的观点。我一直认为,对象的作用域方法完成之后,它们才不会超出范围。当然,除非该对象的作用域在Using块内,或者显式设置为Nothing或null。
乔什大师(Guru Josh)

1
确保他们活着的首选方法是使用GC.KeepAlive(someType);ericlippert.com/2013/06/10/construction-destruction
NotMe 2014年


7

也:

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

7

通常,使用后无需将对象为空,但在某些情况下,我认为这是一种好习惯。

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

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

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


8
好吧,如果已处理的对象已经被处理,则应该抛出ObjectDisposedException。据我所知,这确实需要到处都是样板代码,但是话又说回来,无论如何,Dispose是一个经过深思熟虑的范例。
nicodemus13 2011年

3
按Ctrl + F代表.Dispose()。如果找到它,则说明您没有正确使用IDisposable。一次性物品的唯一用途应该是在使用块的范围内。在使用块之后,您甚至都无法访问myField。在using块中,null不需要设置为,using块将为您配置对象。
Suamere

7

如果您觉得需要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 $$)。公司爆炸了。
Suamere

另一个要注意的是:这是因为c#编译器将找到实现IDisposable的局部作用域变量,并且在作用域结束时将调用.Dispose(时间为MOST)。但是...当从未对.Dispose()进行优化时,SQL连接是一个重要的时期。有些类型需要特别注意,所以我个人总是明确地做事,以免被人咬住。
Suamere

5

仅当变量没有超出范围并且不再需要与之关联的数据时,才应将变量设置为null。否则没有必要。


2
没错,但这也意味着您应该重构代码。我认为我不需要在预期范围之外声明变量。
卡尔·塞金

2
如果“变量”被理解为包括对象字段,那么这个答案很有道理。在“变量”仅表示“局部变量”(一种方法)的情况下,那么我们可能在这里谈论特殊情况(例如,一种运行时间比平时长得多的方法)。
stakx-不再贡献

5

通常不需要设置为null。但是,假设您的课堂上有重置功能。

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

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

最好只用一个单独的标志跟踪它。
Thulani Chivandikwa

3

这种“使用后无需将对象设置为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 AND null out,_PropB以便LazyLoad可以重置以在代码正确时获取正确的值。需要它。

如果您只是这样做,_PropB.Dispose()并且不久之后就期望对LazyLoad的null检查成功,那么它将不会为null,并且您将查看陈旧的数据。实际上,您必须Dispose()在确定之后才将其为空。

我当然希望不是这样,但是我现在有代码在执行Dispose的调用函数的a Dispose()_PropB和之外展示了此行为(因此几乎超出了范围),私有prop仍不为null,并且过时的数据仍然存在。

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

dbkk提到的核心原因是,尽管存在,但父容器(ObjAwith PropB_PropB仍将作用域的实例保持在范围内Dispose()


很好的示例说明了如何手动将null设置为对调用方来说更致命的错误,这是一件好事。

1

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

但是,这种情况仅适用于寿命长的收藏。如果队列无法幸免于创建该函数的末尾,那么它的重要性就小得多。

总的来说,您真的不应该打扰。让编译器和GC进行工作,以便您可以完成工作。



1

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

说:

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

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

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

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

静态字段始终是根对象,因此垃圾回收器始终将它们视为“有效”。如果静态字段引用了不再需要的对象,则应将其设置为null,以便垃圾收集器将其视为有资格进行收集的对象。

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

结论:

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


0

我相信通过GC实现者的设计,您无法通过无效化来加速 GC。我敢肯定,他们宁愿你不是如何/ GC运行时担心自己-把它像这种无处不在的保护和看守,并为你......(弓低着头,拳头提高到天空).. 。

就个人而言,在完成自变量形式的工作后,我经常将变量显式设置为null。我不声明,使用,以后再设置为null -在不再需要它们后立即将其设置为null。我的意思是,明确地说,“我已经正式与您完成了……走了……”

使用GC语言是否需要取消作用?否。这对GC有帮助吗?也许是,也许不是,不确定,通过设计我真的无法控制它,无论今天使用此版本的答案是什么,或将来的GC实现都可能会改变答案,超出我的控制范围。另外,如果/当优化了空值时,如果您愿意,它仅是一个花哨的注释

我认为,这是否会使我的意图更清楚地跟进跟随我的脚步的下一个可怜的傻瓜,并且如果它“可能”有时会帮助GC,那么这对我来说是值得的。通常,它使我感到整洁和清晰,而Mongo喜欢感到整洁和清晰。:)

我是这样看的:编程语言可以使人们了解意图,并且编译器可以完成工作的工作请求-编译器将该请求转换为CPU的另一种语言(有时是几种语言)- CPU可以提供您所使用的语言,选项卡设置,注释,风格重点,变量名等信息。CPU就是位流的全部内容,它告诉它哪些寄存器和操作码以及存储器位置在发生变化。按照我们指定的顺序,用代码编写的许多东西不会转换为CPU消耗的东西。我们的C,C ++,C#,Lisp,Babel,汇编器或任何其他理论而非实际内容均以工作说明的形式编写。所看到的不是获得的结果,是的,即使是汇编语言也是如此。

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

任何规则都有例外。在具有易失性内存,静态内存,竞争条件,单例,使用“过时”数据和所有此类腐烂的情况下,这是不同的:您需要管理自己的内存,以适当的方式锁定和无效,因为内存不是其中的一部分GC'd Universe-希望每个人都能理解。其余时间使用GC语言,这只是样式问题,而不是必要或有保证的性能提升。

在一天结束时,请确保您了解哪些内容符合GC资格,哪些不符合;适当地锁定,处置和废止;上蜡,下蜡;吸气,呼气;对于其他所有内容,我都会说:如果感觉不错,那就去做。您的里程可能会有所不同。


0

我认为将某些内容设置为null会很麻烦。想象一个场景,其中现在设置为通过属性暴露的项目。现在以某种方式在处置该项目后某些代码意外地使用了此属性,您将得到一个null引用异常,需要进行一些调查才能弄清楚到底发生了什么。

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


-1

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


11
不,不是。Dispose()方法确实收集对象-它是用来执行的确定性清理,通常释放非托管资源。
马克·格雷韦尔

1
请记住,确定性仅适用于托管资源,而不适用于非托管资源(即内存)
nicodemus13
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.