"Dispose"只应用于包含非托管资源的类型吗?

Ste*_*unn 65 c# garbage-collection idisposable finalization finalizer

我最近与同事讨论了Dispose实施的价值和类型IDisposable.

我认为即使没有非托管资源可以清理IDisposable,应该尽快清理类型.

我的同事有不同的想法; 执行IDisposable,如果你没有任何非托管资源为你的类型,最终会被垃圾收集是没有必要的.

我的论点是,如果你有一个想要尽快关闭的ADO.NET连接,那么实现IDisposable并且using new MyThingWithAConnection()有意义.我的同事回答说,在封面下,ADO.NET连接是一种非托管资源.我对他回复的答复是,最终所有东西都是一种非托管资源.

我知道推荐的一次性模式,如果Dispose被调用,你可以免费使用托管和非托管资源,但是如果通过终结器/析构函数调用,则只有免费的非托管资源(前面有关于如何提醒消费者不正确使用您的IDisposable类型的博客)

所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable

Vla*_*lad 35

有不同的有效用途IDisposable.一个简单的例子就是保存一个打开的文件,只要你不再需要它就需要在某个时刻关闭.当然,你可以提供一种方法Close,但是使用它Dispose并使用模式using (var f = new MyFile(path)) { /*process it*/ }会更加异常安全.

一个更流行的例子是持有一些其他IDisposable资源,这通常意味着你需要提供自己Dispose的资源以便处理它们.

一般而言,只要您想要确定性地销毁任何东西,就需要实施IDisposable.

我和你的意见之间的区别在于IDisposable,一旦某些资源需要确定性销毁/释放,我就会尽快实施,而不是尽快实现.在这种情况下,依赖垃圾收集不是一种选择(与你同事的说法相反),因为它发生在不可预测的时刻,实际上可能根本不会发生!

任何资源在封面下都不受管理这一事实并不意味着什么:开发人员应该考虑"何时以及如何处理这个对象",而不是"它如何在封面下工作".无论如何,底层实现可能随时间而变化.

实际上,C#和C++之间的主要区别之一是缺少默认的确定性破坏.该IDisposable来缩小差距:您可以订购确定性破坏(虽然你不能保证客户端调用它;在C同样的方式++你不能肯定的是,客户端调用delete的对象).


小补充:确定性释放资源和尽快释放资源之间究竟有什么区别?实际上,那些是不同的(虽然不是完全正交的)概念.

如果要确定性地释放资源,这意味着客户端代码应该有可能说"现在,我希望释放此资源".这可能实际上不是资源可能被释放的最早时刻:持有资源的对象可能已经从资源中获得了所需的一切,因此它可能已经释放资源.另一方面,即使在对象Dispose运行之后,对象也可能选择保留(通常是非托管的)资源,仅在终结器中清理它(如果长时间保持资源不会产生任何问题).

因此,严格来说,为了尽快释放资源Dispose是不必要的:一旦实现自己不再需要资源,对象就可以释放资源.Dispose然而,作为一个有用的提示,不再需要对象本身,因此如果合适的话,也许可以在那时释放资源.


还有一个必要的补充:不仅需要确定性解除分配的非托管资源!这似乎是这个问题答案中意见分歧的关键点之一.人们可以拥有纯粹富有想象力的结构,可能需要确定性地释放.

例如:访问某些共享结构的权利(想想RW锁定),一个巨大的内存块(想象一下你手动管理一些程序的内存),一个使用其他程序的许可证(想象你不被允许不是运行多个X同时这里是一些程序的副本)等被释放的对象不是一个非托管资源,但正确的做/使用的东西,这是一个纯粹的内部构造程序逻辑.


小补充:这里有一小部分[ab]使用的简洁例子IDisposable:http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable.

  • 什么时候需要在文件和数据库连接等非托管资源之外进行确定性破坏?*(也是轻微的抱怨 - 对象本身不是确定性的破坏,只是它使用的资源......并且只有当这些资源不受管理时)* (2认同)
  • @ BlueRaja-DannyPflughoeft:对锁定对象的独占访问*是*资源; 如果一个拥有独占访问权的对象被放弃而没有通知被保护对象不再需要排他性,则任何人都不能再使用该对象.至于事件,如果无限数量的短期对象从长期存在的对象订阅事件但很快就被抛弃,这些对象将创建无限制的内存泄漏,因为它们在生命周期内不会有资格收集.寿命较长的物体. (2认同)

sup*_*cat 17

我认为IDisposable责任的角度考虑是最有帮助的.一个对象应该实现,IDisposable如果它知道在不再需要它和宇宙结束之间需要完成的事情(并且最好是尽快),并且如果它是唯一具有信息和推动力的对象.做到这一点.例如,打开文件的对象将有责任查看该文件是否已关闭.如果对象只是在不关闭文件的情况下消失,则文件可能无法在任何合理的时间范围内关闭.

重要的是要注意,即使只与100%托管对象交互的对象也可以执行需要清理(并且应该使用IDisposable)的事情.例如,IEnumerator附加到集合的"已修改"事件的那个将需要在不再需要时自行分离.否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就永远不会被垃圾收集.如果集合被枚举了一百万次,那么一百万个枚举器将附加到它的事件处理程序.

请注意,有时可以使用终结器进行清理,无论出于何种原因,在没有Dispose首先调用的情况下放弃对象.有时候效果很好; 有些东西很糟糕.例如,即使 Microsoft.VisualBasic.Collection使用终结器将枚举器从"已修改"的事件中分离出来,尝试枚举这样的对象数千次而没有干预Dispose或垃圾收集也会导致它变得非常慢 - 比性能慢许多个数量级如果使用Dispose正确,将导致

  • @SteveDunn:谢谢.似乎普遍认为"非托管资源"这一短语中的"非托管"一词与"非托管代码"有关.现实情况是,这两个概念在很大程度上是正交的.终结者可能会在某种程度上混淆"清理责任"问题,因为如果没有它们,"在宇宙结束之前"语言可能有点文字.如果没有终结器的对象持有句柄的唯一副本,授予对某些东西的独占访问权,并且在不释放句柄的情况下它被放弃,那么句柄将永远不会被释放. (2认同)

kem*_*002 9

所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable?

当有人在一个对象上放置一个IDisposable接口时,这告诉我创建者打算在这个方法中做某事,或者在将来他们可能打算这样做.我总是在这个例子中称为dispose只是为了确定.即使它现在没有做任何事情,它可能在未来,并且因为更新了对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose.

事实上,这是一个判断.你不想过度实现它,因为在那一点上为什么还要烦扰垃圾收集器.为什么不手动处理每个对象.如果您有可能需要处置非托管资源,那么这可能不是一个坏主意.这完全取决于,如果使用您的对象的唯一人员是您团队中的人员,您可以随后跟进他们并说:"嘿,现在需要使用非托管资源.我们必须仔细检查代码并确保我们整理了一下." 如果您要将其发布给其他组织以使用其他组织.没有简单的方法可以告诉每个可能已经实现该对象的人,"嘿,你需要确保现在已经处理掉了." 让我告诉你,有些事情让人们感到羞耻,而不是升级第三方程序集,以发现他们是那些更改代码并使你的应用程序逃避内存问题的人.

我的同事回答说,在幕后,ADO.NET连接是一个托管资源.我对他回复的答复是,最终所有东西都是一种非托管资源.

他是对的,现在是一个托管资源.他们会改变吗?谁知道,但称之为没有伤害.我没有尝试猜测ADO.NET团队做了什么,所以如果他们把它放入并且什么都不做,那就没问题.我仍然会称之为,因为一行代码不会影响我的工作效率.

您还遇到了另一种情况.假设您从方法返回ADO.NET连接.您不知道ADO连接是基础对象还是派生类型.您不知道是否突然需要该IDisposable实现.无论如何,我总是称之为,因为当生产服务器每4小时崩溃时,追踪生产服务器上的内存泄漏就会很糟糕.


Ada*_*son 6

虽然已经有了很好的答案,但我只是想明确一些.

实施有三种情况IDisposable:

  1. 您正在直接使用非托管资源.这通常涉及IntPrt从P/Invoke调用中检索一个或一些其他形式的句柄,该调用必须由不同的P/Invoke调用释放
  2. 您正在使用其他IDisposable对象,需要对其处置负责
  3. 您还有其他需要或使用它,包括using块的便利性.

虽然我可能有点偏见,但您应该真正阅读(并向您的同事展示)StackOverflow WikiIDisposable.

  • 我建议更新Wiki以包含*生命周期管理*作为实现`IDisposable`的理由.例如,`IObservable <T> .Subscribe`返回一个`IDisposable`,即使它不打算封装非托管资源或用在`using`块中. (2认同)

Tig*_*ran 5

不,它不仅 适用于非托管资源.

它被建议像框架调用的基本清理内置机制,使您可以清理所需的任何资源,但最适合的是自然的非托管资源管理.


Jac*_*goń 5

请注意,非托管资源可能包含标准CLR对象,例如保存在某些静态字段中,所有对象都以安全模式运行,根本没有非托管导入.

没有简单的方法可以判断某个给定的类IDiposable实际上是否需要清理某些东西.我的经验法则是总是调用Dispose我不太了解的对象,比如第三方库.


Gab*_*abe 5

Dispose应该用于寿命有限的任何资源.终结器应该用于任何非托管资源.任何非托管资源都应该具有有限的生命周期,但是有大量的托管资源(如锁)也具有有限的生命周期.