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.
sup*_*cat 17
我认为IDisposable
从责任的角度考虑是最有帮助的.一个对象应该实现,IDisposable
如果它知道在不再需要它和宇宙结束之间需要完成的事情(并且最好是尽快),并且如果它是唯一具有信息和推动力的对象.做到这一点.例如,打开文件的对象将有责任查看该文件是否已关闭.如果对象只是在不关闭文件的情况下消失,则文件可能无法在任何合理的时间范围内关闭.
重要的是要注意,即使只与100%托管对象交互的对象也可以执行需要清理(并且应该使用IDisposable
)的事情.例如,IEnumerator
附加到集合的"已修改"事件的那个将需要在不再需要时自行分离.否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就永远不会被垃圾收集.如果集合被枚举了一百万次,那么一百万个枚举器将附加到它的事件处理程序.
请注意,有时可以使用终结器进行清理,无论出于何种原因,在没有Dispose
首先调用的情况下放弃对象.有时候效果很好; 有些东西很糟糕.例如,即使 Microsoft.VisualBasic.Collection
使用终结器将枚举器从"已修改"的事件中分离出来,尝试枚举这样的对象数千次而没有干预Dispose
或垃圾收集也会导致它变得非常慢 - 比性能慢许多个数量级如果使用Dispose
正确,将导致
所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable?
当有人在一个对象上放置一个IDisposable接口时,这告诉我创建者打算在这个方法中做某事,或者在将来他们可能打算这样做.我总是在这个例子中称为dispose只是为了确定.即使它现在没有做任何事情,它可能在未来,并且因为更新了对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose.
事实上,这是一个判断.你不想过度实现它,因为在那一点上为什么还要烦扰垃圾收集器.为什么不手动处理每个对象.如果您有可能需要处置非托管资源,那么这可能不是一个坏主意.这完全取决于,如果使用您的对象的唯一人员是您团队中的人员,您可以随后跟进他们并说:"嘿,现在需要使用非托管资源.我们必须仔细检查代码并确保我们整理了一下." 如果您要将其发布给其他组织以使用其他组织.没有简单的方法可以告诉每个可能已经实现该对象的人,"嘿,你需要确保现在已经处理掉了." 让我告诉你,有些事情让人们感到羞耻,而不是升级第三方程序集,以发现他们是那些更改代码并使你的应用程序逃避内存问题的人.
我的同事回答说,在幕后,ADO.NET连接是一个托管资源.我对他回复的答复是,最终所有东西都是一种非托管资源.
他是对的,现在是一个托管资源.他们会改变吗?谁知道,但称之为没有伤害.我没有尝试猜测ADO.NET团队做了什么,所以如果他们把它放入并且什么都不做,那就没问题.我仍然会称之为,因为一行代码不会影响我的工作效率.
您还遇到了另一种情况.假设您从方法返回ADO.NET连接.您不知道ADO连接是基础对象还是派生类型.您不知道是否突然需要该IDisposable实现.无论如何,我总是称之为,因为当生产服务器每4小时崩溃时,追踪生产服务器上的内存泄漏就会很糟糕.
虽然已经有了很好的答案,但我只是想明确一些.
实施有三种情况IDisposable
:
IntPrt
从P/Invoke调用中检索一个或一些其他形式的句柄,该调用必须由不同的P/Invoke调用释放IDisposable
对象,需要对其处置负责using
块的便利性.虽然我可能有点偏见,但您应该真正阅读(并向您的同事展示)StackOverflow WikiIDisposable
.
请注意,非托管资源可能包含标准CLR对象,例如保存在某些静态字段中,所有对象都以安全模式运行,根本没有非托管导入.
没有简单的方法可以判断某个给定的类IDiposable
实际上是否需要清理某些东西.我的经验法则是总是调用Dispose
我不太了解的对象,比如第三方库.