我最近与一位同事讨论了Dispose的价值以及实现IDisposable的类型。
我认为,为应该尽快清理的类型实现IDisposable是有价值的,即使没有非托管资源可以清理。
我的同事有不同的想法;如果您没有任何非托管资源,则不需要实现IDisposable,因为您的类型最终将被垃圾收集。
我的观点是,如果您希望尽快关闭ADO.NET连接,那么实现IDisposable和using new MyThingWithAConnection()是有意义的。我的同事回答说,在封面下,ado.net连接是非托管资源。我对他的回答是,一切最终都是一种非托管资源。
我知道建议的一次性模式,如果调用Dispose,则释放托管和非托管资源,但如果通过终结器/析构函数调用,则仅释放非托管资源(不久前还写了一篇关于如何警告消费者不正确使用IDisposable类型的日志)。
所以,我的问题是,如果您有一个不包含非托管资源的类型,那么实现IDisposable值得吗?
- 正如您正确指出的,ADO连接是非托管资源。
- @不是。连接称为托管资源。它包含(拥有)一个非托管资源,尽管可能是通过SafeHandle间接拥有的。
- @这就是我的意思——我应该更仔细地措辞,但在这个问题上,它已经用正确的方式措辞了。
- 除非托管资源外,我唯一需要的时间是确保事件得到正确取消订阅,以便类可以被垃圾收集。但这确实是语言的一个失败:事件确实需要弱引用,但事实并非如此。
IDisposable有不同的有效用途。一个简单的例子是保存一个打开的文件,一旦不再需要它,就需要在某个时刻关闭它。当然,您可以提供一个方法Close,但是在Dispose中使用它,使用像using (var f = new MyFile(path)) { /*process it*/ }这样的模式会更安全。
一个比较流行的例子是持有一些其他的IDisposable资源,这通常意味着您需要提供自己的Dispose资源来处理它们。
一般来说,只要您想要对任何东西进行确定性破坏,就需要实现IDisposable。
我和你的观点的不同之处在于,我在某些资源需要确定性破坏/释放时立即执行IDisposable,而不是尽快执行。在这种情况下,依赖垃圾收集不是一个选项(与同事的说法相反),因为它发生在不可预知的时刻,实际上可能根本不会发生!
事实上,任何资源在保护层下都是不受管理的,这并不意味着什么:开发人员应该考虑"何时以及如何处置该对象",而不是"如何在保护层下工作"。不管怎样,底层实现可能会随着时间而改变。
事实上,C和C++之间的主要区别之一是没有默认的确定性破坏。EDCOX1的"0"来关闭这个间隙:您可以命令确定性的破坏(虽然不能保证客户端正在调用它,但是在C++中,同样不能确保客户端在对象上调用EDCOX1×9)。
小补充:事实上,确定性释放资源和尽快释放资源有什么区别?实际上,这些概念是不同的(虽然不是完全正交的)。
如果要确定地释放资源,这意味着客户机代码应该可以说"现在,我希望释放这个资源"。实际上,这可能不是释放资源的最早时刻:持有资源的对象可能从资源中获得了所需的一切,因此可能已经释放了资源。另一方面,对象可能会选择保留(通常是非托管的)资源,即使在对象的Dispose运行完之后,也只在终结器中清理它(如果保留资源太长时间不会造成任何问题)。
因此,为了尽快释放资源,严格地说,Dispose不是必要的:对象一旦意识到不再需要资源,就可以释放资源。但是,Dispose提供了一个有用的提示,即不再需要对象本身,因此,如果合适的话,可以在该点释放资源。
还有一个必要的补充:不仅非托管资源需要确定性释放!这似乎是这个问题答案之间意见分歧的一个关键点。一个人可以有纯粹的想象结构,这可能需要确定性地释放。
例如:访问某些共享结构的权限(想想rw锁)、一个巨大的内存块(想想你正在手动管理程序的一些内存)、使用其他程序的许可证(想想你不允许同时运行某些程序的x个以上的副本)等等。在这里,要释放的对象不是非托管对象。源代码,但有权做/使用某些东西,这是程序逻辑的一个纯粹的内部构造。
小补充:下面是一小部分使用IDisposable的[ab]简单示例:http://www.intorx.com/content/v1.0.10621.0/03摼lifetimemanagement.html_idisposable。
- 在非托管资源(如文件和数据库连接)之外,什么时候需要进行确定性破坏?(也是次要的gripe-对象本身不是确定性破坏的,只有它使用的资源…只有在这些资源不受管理的情况下)
- @blueraja dannypflughoeft:看我的答案,但是直接回答:1)当你通过其他实现IDisposable的对象间接使用这些资源时,或者当IDisposable习惯用法(以及using块的便利性)出于任何原因被使用时。
- @亚当:情况1就是有非托管资源需要清理的情况。案例2与我的问题是正交的,无论如何也不是一个真正的例子。
- @blueraja dannypflughoeft:是的,尽管模式不同,这取决于您是直接使用非托管资源还是通过抽象来使用。至于第二种模式,任何有入口和出口的模式都是合适的。虽然lock已经存在,但很容易用using重现同样的行为。它还可以用于那些您临时更改状态并希望确保它被更改回来的情况…例如,更改鼠标光标。您可以在构造函数中更改它,并将其重置为Dispose中的任何内容。
- @BlueRaja:任何资源都可能需要确定性释放。它可能是任务表中的一个槽、巨大的内存块、协作工作者的环形队列中的令牌,等等。
- @blueraja dannypflughoeft:我希望人们会说"非托管资源,如文件等",因为完全在托管代码领域内是可能的,需要确定性清理。锁和强事件之类的东西是需要非基于GC的清理的资源,但它们不访问托管框架之外的任何内容。
- @supercat:释放锁并回收其资源不是同一回事。锁和强事件都不需要确定性破坏。
- @blueraja dannypflughoeft:对锁定对象的独占访问是一种资源;如果拥有独占访问权的对象被放弃而没有通知被保护对象不再需要独占性,那么就没有人能够再使用该对象了。至于事件,如果无限数量的短期对象订阅了长期对象的事件,但很快就被放弃了,那么这些对象将创建无限内存泄漏,因为它们在长期对象的生存期内不符合收集条件。
我认为从责任的角度来考虑IDisposable是最有帮助的。如果一个物体知道在它不再需要的时间和宇宙的尽头(最好尽快)之间需要做的事情,并且它是唯一既有信息又有动力去做的物体,那么它应该实现IDisposable。例如,打开文件的对象有责任查看文件是否关闭。如果对象只是消失而不关闭文件,那么文件可能不会在任何合理的时间段内关闭。
需要注意的是,即使只与100%托管对象交互的对象也可以执行需要清理的操作(并且应该使用IDisposable)。例如,一个附加到集合的"modified"事件的IEnumerator需要在不再需要时分离自身。否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器将永远不会被垃圾收集。如果集合被枚举一百万次,一百万个枚举器将附加到它的事件处理程序。
请注意,有时可以使用终结器进行清理,以防由于任何原因,对象在没有首先调用Dispose的情况下被放弃。有时效果很好,有时效果很差。例如,即使Microsoft.VisualBasic.Collection使用终结器将枚举器与"修改的"事件分离,尝试在没有干预Dispose或垃圾收集的情况下对这样的对象进行数千次枚举,也会导致它变得非常慢——比使用Dispose时所导致的性能慢很多个数量级。正确地。
- 谢谢,我希望我能把IEnumerator的例子当作反驳!
- @史蒂文顿:谢谢。人们似乎普遍认为"非托管资源"一词中的"非托管"一词与"非托管代码"有关。事实上,这两个概念在很大程度上是正交的。终结器可能会有些混淆"清理责任"问题,因为没有它们,"宇宙末日之前"的语言可能有些文字化。如果没有终结器的对象拥有授予对某个对象独占访问权的句柄的唯一副本,并且在不释放该句柄的情况下被放弃,则该句柄将永远不会被释放。
- @史蒂文顿:当然,在宇宙末日之前,手柄是否被释放的问题可能变得不太明显,但关键是,一旦所有的拷贝都消失了,就不会有任何东西释放手柄。因此,具有其副本的最后一个实体必须确保在该句柄副本仍然存在时释放它。顺便说一下,完全在托管代码中的"非托管资源"的另一个好例子是:锁。
- even objects which only interact with 100% unmanaged objects不应该读取100%的托管对象吗?否则,回答得很好。如果您的实现拥有一个可IDisposable的实例,那么您也应该实现它来清理它,IDisposable是唯一的通信方式。
- 安迪:固定了。我的主要观点是,很多人似乎认为"非托管资源"一词意味着"由本机代码处理的资源",而事实并非如此。虽然由本机代码管理的资源几乎总是非托管资源,但它们并非唯一重要的类型。实际上,我真的不喜欢"托管资源"和"非托管资源"这两个词,因为它们的含义几乎没有一致性。是一个本身不操纵任何外部事物的"托管资源",还是这个术语只指带有终结器的对象?
- fwiw,我不知道任何以这种方式监视集合的枚举器。例如,List的枚举器只检查列表中的version变量。不过,这是一个有效的观点。
- @Adamrobinson:当集合被修改时,Microsoft.VisualBasic.Collection类的枚举器订阅通知,并使用这些通知以便它可以继续敏感地工作。Collection类确实包含逻辑,以确保如果放弃枚举器,订阅将在下一个GC上被清除,但是如果在垃圾收集周期之间枚举Collection多次(例如数千次),它将开始变得非常慢。一旦发生下一个收集,它将加速备份。
- @超级卫星:有趣!还有另一个处置IDisposable的原因
- adamrobinson @:incidentally,而"aforementioned Collection对象腹部的一些annoying quirks,限制其用途为usefulness超过历史compatibility,它也有一些独特的advantages。长官垂死的,它是唯一词典- ISH的收集,使它容易捕捉所有的项目会议的某些标准,没有建立起来的具有到separate名单项目捕捉。
So, my question is, if you've got a type that doesn't contain
unmanaged resources, is it worth implementing IDisposable?
当有人在一个对象上放置IDisposable接口时,这告诉我创建者打算这样做,要么在该方法中做一些事情,要么在将来他们可能打算这样做。在这种情况下,我总是调用Dispose来确保这一点。即使它现在不做任何事情,将来也可能发生,而且由于它们更新了一个对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose。
事实上,这是一个判断的召唤。您不想过度实现它,因为在这一点上,为什么还要麻烦有一个垃圾收集器呢?为什么不手动处理每个对象呢?如果有可能需要释放非托管资源,那么这可能不是一个坏主意。这完全取决于,如果唯一使用对象的人是您的团队中的人,那么您可以稍后跟进他们并说,"嘿,现在需要使用非托管资源。我们必须仔细检查代码,确保我们已经整理好了。"如果您要发布此代码供其他组织使用,这是不同的。要告诉所有可能实现了该对象的人,"嘿,你需要确保它现在已经被处理掉。"让我告诉你,没有什么比升级第三方程序集更让人疯狂的事情了,因为他们是那些更改了代码并使你的应用程序摆脱内存问题的人。
My colleage replied that, under the covers, an ADO.NET connection is a
managed resource. My reply to his reply was that everything ultimately
is an unmanaged resource.
他是对的,现在这是一个管理资源。他们会改变吗?谁知道呢,但打电话也不痛。我不想猜测ADO.NET团队做了什么,所以如果他们把它放进去,它什么也不做,那就好了。我还是会这样称呼它,因为一行代码不会影响我的工作效率。
您还遇到了另一个场景。假设您从一个方法返回一个ADO.NET连接。您不知道ADO连接是基本对象或派生类型。您不知道IDisposable实现是否突然变得必要。不管怎样,我总是称之为它,因为跟踪生产服务器上的内存泄漏在每4小时崩溃时很糟糕。
- 许多类型实现IDisposable,不是因为它们希望将来的版本可以这样做,而是因为它们或基类型可以用作工厂方法的返回类型,工厂方法可能返回需要清除的派生类型。所有实现IEnumerator的类型,例如,实现IDisposable的所有类型,即使它们的大多数Dispose方法都不起作用。
虽然已经有了很好的答案,但我只想做些明确的事情。
实施IDisposable有三种情况:
您正在直接使用非托管资源。这通常涉及从必须由不同的p/invoke调用释放的p/invoke调用中检索IntPrt或其他形式的句柄。
您正在使用其他IDisposable对象,需要对它们的处理负责。
您还需要或使用它,包括using块的便利性。
虽然我可能有点偏颇,但你应该阅读(并向你的同事展示)IDisposable上的stackoverflow wiki。
- 我推荐的updating维基包括寿命管理的原因,到implement IDisposable。例如,一个IObservable.Subscribereturns IDisposable即使,虽然这不是用于到encapsulate unmanaged资源或是用在usingblocks。
- 加布:@ 维基,所以感觉到自由编辑。我没有用T IObservable之前,所以它可能会更好,如果你能给我的东西。
- adamrobinson @:应该有一些clarification关于"永远"的召唤,IDisposable。它的重要的是Dispose叫进来之前的最后一个参考一IDisposable也destroyed(因为它不应该叫后)。在另一方面,它的多为常见的物体到月亮references一IDisposable;一般,到底应该叫一个Dispose。
- supercat @:是的,你会是的先生。应该有一个"业主"为任何在IDisposable,responsible为ensuring,Dispose得到appropriately叫。
Dispose应用于寿命有限的任何资源。终结器应用于任何非托管资源。任何非托管资源的生命周期都应该是有限的,但也有许多托管资源(如锁)的生命周期也是有限的。
注意,非托管资源很可能包括标准的clr对象,例如保存在某些静态字段中,所有这些对象都在安全模式下运行,根本没有非托管导入。
没有简单的方法来判断实现IDiposable的给定类是否确实需要清理某些东西。我的经验法则是对我不太熟悉的对象,比如第三方库,总是叫Dispose。
- 值得注意的是,即使对象具有DisposeRequired属性,发现Dispose是否有必要所需的时间(稍微)会超过无条件调用Dispose所需的时间(因为它必须对该属性进行虚拟调用,然后对结果进行分支,而不是简单地进行虚拟调用)。打电话给我。只有当确定何时调用Dispose比实际调用它更麻烦时(例如,如果需要清理的对象需要"用户计数",而不是那些不需要清理的对象需要"用户计数"),DisposeRequired才有帮助。
不,它不仅用于非托管资源。
它类似于框架调用的基本清理内置机制,允许您清理所需的任何资源,但它最适合的是自然的非托管资源管理。
- 实现(和调用)Dispose对于大多数资源持有类都是至关重要的。托管/非托管通常是不相关的。
- @亨霍尔特曼:对我来说,这似乎就是我真正的意思:"这不仅仅是为了管理不好的资源管理。"是吗?
- 是的,对不起。我忽略了一个不在那里。
everything ultimately is an unmanaged resource.
不是真的。除CLR对象使用的内存外,所有内容都只能由框架管理(分配和释放)。
在一个对象上实现IDisposable和调用Dispose是毫无意义的,该对象不支持任何非托管资源(直接或间接通过依赖对象)。它不会使释放对象具有确定性,因为您不能直接释放对象的clr内存,因为只有GC可以这样做。对象是引用类型,因为值类型直接用于方法级别时,由堆栈操作分配/释放。
现在,每个人都声称他们的答案是正确的。让我来证明我的。根据文件:
Object.Finalize Method allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
换句话说,对象的clr内存在调用Object.Finalize()之后释放。[注意:如果需要,可以显式跳过此调用]
这里是一个没有非托管资源的可释放类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| internal class Class1 : IDisposable
{
public Class1()
{
Console.WriteLine("Construct");
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
~Class1()
{
Console.WriteLine("Destruct");
}
} |
注意,析构函数隐式调用继承链中的每个Finalize,直到Object.Finalize()。
下面是控制台应用程序的Main方法:
1 2 3 4 5 6 7 8 9 10
| static void Main (string[] args )
{
for (int i = 0; i < 10; i ++)
{
Class1 obj = new Class1 ();
obj .Dispose();
}
Console .ReadKey();
} |
如果调用Dispose是一种以确定性方式释放托管对象的方法,那么每个"dispose"后面都会紧跟一个"destruct",对吗?你自己看看会发生什么。从命令行窗口运行这个应用程序是最有趣的。
注意:有一种方法可以强制GC收集当前应用程序域中等待完成但单个特定对象没有的所有对象。不过,您不需要调用Dispose在终结队列中有一个对象。强烈建议不要强制收集,因为它可能会损害整个应用程序性能。
编辑
有一个例外——状态管理。如果对象恰好管理外部状态,则Dispose可以处理状态更改。即使状态不是非托管对象,也可以很方便地像使用它一样使用它,因为IDisposable有特殊的处理方法。例如安全上下文或模拟上下文。
1 2 3 4 5 6
| using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
{
// do something as SomeUser
}
// back to your user |
这不是最好的例子,因为WindowsImpersonationContext在内部使用系统句柄,但您可以了解到这一点。
最重要的是,在实现IDisposable时,您需要(或计划)在Dispose方法中做一些有意义的事情。否则就是浪费时间。IDisposable不会改变GC管理对象的方式。
- 你是不是就在这里。"对象"是不可能physically"垃圾collected,但它是免费的logically将是重要的在认识的时间。这个"东西"可能不是唯一的unmanaged一些资源,但也是一个插槽在queue,中的threadpool线程为一些calculation许可,为使用一些其他的计划等。在公元前的逻辑资源。* * *的presence对象在记忆的纯净的细节和不应该采取到帐户,如果disposable对象也implemented在正确的方式。
- 弗拉德@你错过一些零件的答案。所有的那些"东西",你是mentioned到底unmanaged资源。如果你的交易对象与那些(直接或通过indirectly dependent物体),它变成一个unmanaged的一个和我implement Dispose。在其他的话,如果任何dependent对象implements Dispose你的对象应该为好。对不起,如果这是不明确pointed。
- 嗯,我不是questioning整你的答案,这只是"海岸"的呼唤dispose在一个对象,并坚持不到任何unmanaged资源(……)pointless"。我想得到一些examples泼水]资源也essentially managed,虽然需要去释放在deterministic方式。例如,正确的modify收集到的也不是一个明确unmanaged资源,但obtaining这权利在我的raii方式与IDisposable也是一个好东西:using (ObtainModifyRight(collection)) { /* modify it */ }。
- managing状态用dispose的位的hijacking dispose pattern比它也意味着为unmanaged资源。但我同意这happens和有时也convenient因为因为usingkeyword。updating回答。
- 正确的modify收集到的是一个unmanaged资源如果bestowal这样就在一个对象将impair其他物体的能力"到modify,收集的对象,直到收到这样一个正确的indicates它也不再需要。毕竟,什么这真的意味着为一个request对象到块的unmanaged记忆从操作系统的,除了那个(1)*一个*给合适的对象,使用到的记忆,和(2)其他人将可以使用到它,直到第一个对象告诉《IT不再需要它???????
- @ Maciej -尽"pointless"去评论,也许你应该读一读我的其他explanations到其他邮电我联到我的答案。至于"proving"你的回答,文件一事,但除非你已经debugged oom例外dumps记忆与windbg和有知识的内部workings之气,你应该依赖上的经验比文件。
你是对的。托管数据库连接、文件、注册表项、套接字等都保留在非托管对象上。这就是他们实现IDisposable的原因。如果您的类型拥有一次性对象,那么您应该实现IDisposable,并使用Dispose方法处理它们。否则,它们可能会一直保持活动状态,直到垃圾被收集,从而导致锁定的文件和其他意外行为。
- 呃,听起来你同意欧普的同事,而不是欧普。
- 嗯,我假设同事认为,如果您的类型具有托管ADO连接,则不必实现IDisposable,因为资源是托管的。我要说的是,您必须这样做,因为在任何IDisposable对象的内部都有一个非托管资源。当您聚合IDisposable对象时,还必须实现IDisposable。
- 在这种情况下,需要清理非托管资源。同事认为,如果没有非托管资源,就不需要IDisposable--op似乎认为"即使没有非托管资源需要清理,实现IDisposable…]也有价值。"
- @我想这个问题有点含糊不清。我的问题主要是对我对他的回答的回应,那就是一切最终都是一种非托管资源。我支持这一说法。
如果聚合IDisposable,那么应该实现接口,以便及时清理这些成员。在您引用的ADO.NET连接示例中,如何调用myConn.Dispose()?
不过,我认为在这种情况下,说任何东西都是非托管资源是不正确的。我也不同意你的同事。
简短的回答:绝对不是。如果类型具有托管或非托管的成员,则应实现IDisposable。
现在细节:我已经回答了这个问题,并提供了更多关于内存管理内部的详细信息,以及关于StackOverflow中的问题的GC。以下是一些:
- 依赖.NET自动垃圾收集器是一种坏做法吗?
- 如果我不调用笔对象上的Dispose,会发生什么?
- Dispose,何时调用?
关于实现IDisposable的最佳实践,请参阅我的博客文章:
如何正确实现IDisposable模式?
在我的一个项目中,我有一个包含托管线程的类,我们将它们称为线程A和线程B,以及IDisposable对象,我们将其称为C。
a用于在退出时释放c。b用于使用c保存异常。
我的类必须实现IDisposable和Descrutctor,以确保按正确的顺序处理事情。是的,GC可以清理我的物品,但我的经验是,除非我能清理我的班级,否则会有一个比赛条件。
如果类型引用了非托管资源或包含对实现IDisposable的对象的引用,则该类型应实现IDisposable。
根本不需要资源(托管或非托管)。通常情况下,IDisposable只是消除try {..} finally {..}组合的一种方便方法,只是比较一下:
1 2 3 4 5 6 7 8 9 10
| Cursor savedCursor = Cursor.Current;
try {
Cursor.Current = Cursors.WaitCursor;
SomeLongOperation();
}
finally {
Cursor.Current = savedCursor;
} |
具有
1 2 3
| using (new WaitCursor ()) {
SomeLongOperation ();
} |
其中WaitCursor是IDisposable适用于using的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public sealed class WaitCursor: IDisposable {
private Cursor m_Saved;
public Boolean Disposed {
get;
private set;
}
public WaitCursor() {
Cursor m_Saved = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
}
public void Dispose() {
if (!Disposed) {
Disposed = true;
Cursor.Current = m_Saved;
}
}
} |
您可以轻松地组合这些类:
1 2 3 4 5 6 7
| using (new WaitCursor ()) {
using (new RegisterServerLongOperation ("My Long DB Operation")) {
SomeLongRdbmsOperation ();
}
SomeLongOperation ();
} |
如果对象拥有任何非托管对象或任何托管可释放对象,则实现IDisposable。
如果一个对象使用非托管资源,它应该实现IDisposable。拥有可释放对象的对象应该实现IDisposable,以确保底层非托管资源被释放。如果遵循规则/约定,那么合理的结论是,不处理托管可释放对象等于不释放非托管资源。