我最近与一位同事讨论了 Dispose
的 Value 以及实施 IDisposable
的类型 .
我认为实施 IDisposable
对于应该尽快清理的类型是有 Value 的, even if there are no unmanaged resources to clean up .
我的同事有不同的想法;实施 IDisposable
如果你没有必要,因为你的类型最终将被垃圾收集 .
我的论点是,如果你想要尽快关闭ADO.NET连接,那么实现 IDisposable
和 using new MyThingWithAConnection()
就有意义了 . 我的同事回答说,在封面下,ADO.NET连接是 unmanaged resource . 我对他的答复的答复是 everything ultimately is an unmanaged resource .
我知道recommended disposable pattern你在哪里 free managed and unmanaged resources if Dispose is called 但是 only free unmanaged resources if called via the finalizer/destructor (并且刚刚发表了关于如何alert consumers of improper use of your IDisposable types的博客)
所以,我的问题是,如果你包含非托管资源,是否值得实施 IDisposable
?
15 回答
IDisposable
有不同的有效用途 . 一个简单的例子就是保存一个打开的文件,只要你不再需要它就需要在某个时刻关闭 . 当然,您可以提供方法Close
,但在Dispose
中使用它并使用类似using (var f = new MyFile(path)) { /*process it*/ }
的模式将更加异常安全 .一个更流行的例子是持有一些其他
IDisposable
资源,这通常意味着你需要提供自己的Dispose
以便处理它们 .通常,只要您想要确定性地销毁任何东西,就需要实现
IDisposable
.我和你的意见之间的区别在于,一旦某些资源需要确定性销毁/释放,我就会尽快实施
IDisposable
,而不是必须尽快实现 . 在这种情况下,依赖垃圾收集不是一种选择(与你同事的说法相反),因为它发生在不可预测的时刻,实际上可能根本不会发生!任何资源在封面下都不受管理这一事实并不意味着什么:开发人员应该考虑“何时以及如何处理这个对象”,而不是“它如何在封面下工作” . 无论如何,底层实现可能随时间而变化 .
实际上,C#和C之间的主要区别之一是缺少默认的确定性破坏 .
IDisposable
缩小了差距:您可以订购确定性销毁(虽然您无法确保客户端正在调用它;但在C中您也无法确定客户端在对象上调用delete
) .小补充:确定性释放资源和尽快释放资源之间究竟有什么区别?实际上,那些是不同的(虽然不是完全正交的)概念 .
如果要确定性地释放资源,这意味着客户端代码应该有可能说"Now, I want this resource freed" . 这可能实际上不是资源可能被释放的最早时刻:持有资源的对象可能已经从资源中获得了所需的一切,因此它可能已经释放资源 . 另一方面,即使在对象的
Dispose
运行之后,对象也可能选择保留(通常是非托管的)资源,仅在终结器中清理它(如果长时间保持资源不会产生任何问题) .因此,为了尽快释放资源,严格来说,
Dispose
不是必需的:一旦实现自己不再需要资源,对象就可以释放资源 . 然而,Dispose
作为一个有用的暗示,不再需要对象本身,所以如果合适的话,也许可以在那时释放资源 .还有一个必要的补充:不仅需要确定性解除分配的非托管资源!这似乎是这个问题答案中意见分歧的关键点之一 . 人们可以拥有纯粹富有想象力的结构,可能需要确定性地释放 .
例如:访问某些共享结构的权利(想想RW-lock),一个巨大的内存块(想象一下你手动管理一些程序的内存),一个使用其他程序的许可证(想象你不允许运行更多比起某些程序的X副本同时)等 . 这里要释放的对象不是非托管资源,而是做/使用某事的权利,这是纯粹的内在构造你的程序逻辑 .
小补充:这里有一小部分[ab]使用
IDisposable
:http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable的简洁例子 .我认为在责任方面考虑
IDisposable
是最有帮助的 . 一个对象应该实现IDisposable
,如果它知道在它之间需要完成的事情,那么它就是唯一具有信息和推动力的对象 . 例如,打开文件的对象将有责任查看该文件是否已关闭 . 如果对象只是在不关闭文件的情况下消失,则文件可能无法在任何合理的时间范围内关闭 .重要的是要注意,即使只与100%托管对象交互的对象也可以执行需要清理的事情(并且应该使用
IDisposable
) . 例如,附加到集合的"modified"事件的IEnumerator
将需要在不再需要时自行分离 . 否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就永远不会被垃圾收集 . 如果集合被枚举了一百万次,那么一百万个枚举器将附加到它的事件处理程序 .请注意,有时可以使用终结器进行清理,无论出于何种原因,在没有首先调用
Dispose
的情况下放弃对象 . 有时候效果很好;有些东西很糟糕 . 例如,即使Microsoft.VisualBasic.Collection
使用终结器从"modified"事件中分离枚举器,尝试枚举这样的对象数千次而没有干预Dispose
或垃圾收集将导致它变得非常慢 - 比性能慢许多个数量级如果正确使用Dispose
会导致根本没有必要的资源(托管或非托管) . 通常,
IDisposable
只是一种方便的方法来消除精炼try {..} finally {..}
,只需比较:同
其中
WaitCursor
是IDisposable
适合using
:您可以轻松地组合这些类:
当有人在一个对象上放置一个IDisposable接口时,这告诉我创建者打算在这个方法中做某事,或者在将来他们可能打算这样做 . 我总是在这个例子中称为dispose只是为了确定 . 即使它现在没有做任何事情,它可能在未来,并且因为更新了对象而导致内存泄漏很糟糕,并且在第一次编写代码时没有调用Dispose .
事实上,这是一个判断 . 你不想过度实现它,因为在那一点上为什么还要烦扰垃圾收集器 . 为什么不手动处理每个对象 . 如果您有可能需要处置非托管资源,那么这可能不是一个坏主意 . 这完全取决于,如果使用您的对象的唯一人员是您团队中的人员,您可以随后跟进他们并说:“嘿,现在需要使用非托管资源 . 我们必须仔细检查代码并确保我们整理了一下 . “如果您要将其发布给其他组织以使用其他组织 . 没有简单的方法可以告诉每个可能已经实现该对象的人,“嘿,你需要确保现在已经处理掉了 . ”让我告诉你,有些事情让人们感到羞耻,而不是升级第三方程序集,以发现他们是那些更改代码并使你的应用程序逃避内存问题的人 .
他是对的,现在是一个托管资源 . 他们会改变吗?谁知道,但称之为没有伤害 . 我没有尝试猜测ADO.NET团队做了什么,所以如果他们把它放入并且什么都不做,那就没问题 . 我仍然会称之为,因为一行代码不会影响我的工作效率 .
您还遇到了另一种情况 . 假设您从方法返回ADO.NET连接 . 您不知道ADO连接是基础对象还是派生类型 . 您不知道是否突然需要该IDisposable实现 . 无论如何,我总是称之为,因为当 生产环境 服务器每4小时崩溃时,追踪 生产环境 服务器上的内存泄漏就会很糟糕 .
虽然已经有了很好的答案,但我只是想明确一些 .
实施
IDisposable
有三种情况:您正在直接使用非托管资源 . 这通常涉及从P / Invoke调用中检索
IntPrt
或其他形式的句柄必须通过不同的P / Invoke调用发布您正在使用其他
IDisposable
对象,需要对其处置负责您还有其他需要或使用它,包括
using
块的便利性 .虽然我可能有点偏颇,但你应该阅读(并向你的同事展示)the StackOverflow Wiki on IDisposable .
Dispose
应该用于具有有限生命期的任何资源 . 终结器应该用于任何非托管资源 . 任何非托管资源都应该具有有限的生命周期,但是有大量的托管资源(如锁)也具有有限的生命周期 .请注意,非托管资源可能包含标准CLR对象,例如保存在某些静态字段中,所有对象都以安全模式运行,根本没有非托管导入 .
没有简单的方法可以判断实现
IDiposable
的给定类是否确实需要清理某些东西 . 我的经验法则是总是在我不太了解的对象上调用Dispose
,就像某些第三方库一样 .不,它只是非托管资源的 not .
它被建议像框架调用的基本清理内置机制,使您可以清理所需的任何资源,但最适合的是自然的非托管资源管理 .
如果你聚合
IDisposable
然后你应该实现接口,以便及时清理这些成员 . 如何在你引用的ADO.Net连接示例中调用myConn.Dispose()
?在这种情况下,我不能正确地说一切都是非托管资源 . 我也不同意你的同事 .
你是对的 . 托管数据库连接,文件,注册表项,套接字等都保留在非托管对象上 . 这就是他们实施
IDisposable
的原因 . 如果您的类型拥有一次性对象,则应实现IDisposable
并将其置于Dispose
方法中 . 否则,他们可能会保持活着,直到收集垃圾导致锁定文件和其他意外行为 .不对 . CLR对象使用的内存以外的所有内容,只由框架管理(分配和释放) .
实现
IDisposable
并调用Dispose
on an object that does not hold on to any unmanaged resources (直接或间接通过依赖对象)是 pointless . 它确实释放了该对象 deterministic ,因为它始终只有GC
这样做 . 对象是引用类型,因为值类型在直接在方法级别使用时,由堆栈操作分配/释放 .现在,每个人都声称他们的答案是正确的 . 让我 prove 我的 . 根据documentation:
换句话说,在调用
Object.Finalize()
之后释放对象的CLR内存 . [注意:如果需要,可以明确跳过此调用]这是一个没有非托管资源的一次性类:
请注意destructor隐式调用继承链中的每个
Finalize
到Object.Finalize()
这是控制台应用程序的
Main
方法:如果调用
Dispose
是一种以确定的方式释放 managed 对象的方法,那么每个"Dispose"会紧跟一个"Destruct",对吧?看看自己会发生什么 . 从命令行窗口运行此应用程序是最有趣的 .注意:有一种方法可以强制
GC
收集当前应用程序域中待定位的所有对象,但对于单个特定对象则不会 . 不过,您无需调用Dispose
即可在终结队列中拥有对象 . 强烈建议不要强行收集,否则可能会影响整体应用程序的性能 .EDIT
有一个例外 - 国家管理 . 如果您的对象恰好管理外部状态,
Dispose
可以处理状态更改 . 即使状态不是非托管对象,由于特殊处理_1147474_,因此使用它非常方便 . 示例将是安全上下文或模拟上下文 .这不是最好的例子,因为
WindowsImpersonationContext
在内部使用了系统句柄,但是你得到了图片 .底线是,在实施
IDisposable
时,您需要(或计划拥有)Dispose
方法中有意义的事情 . 否则,这只是浪费时间 .IDisposable
确实不会更改GC管理对象的方式 .如果类型引用非托管资源,或者它包含对实现IDisposable的对象的引用,则应该实现IDisposable .
在我的一个项目中,我有一个带有托管线程的类,我们将它们称为线程A,线程B和IDisposable对象,我们称之为C.
用于处理退出时的C. B曾经使用C来保存异常 .
我的 class 必须实施IDisposable和descrtuctor,以确保按正确的顺序处理事情 . 是的,GC可以清理我的物品,但我的经验是有一个竞争条件,除非我管理我的 class 清理 .
简答:绝对不是 . 如果您的类型具有托管或非托管成员,则应实现IDisposable .
现在详细说明:我已回答了这个问题,并在StackOverflow上提供了有关内存管理内容和GC问题的更多细节 . 这里仅仅是少数:
Is it bad practice to depend on the .NET automated garbage collector?
What happens if I don't call Dispose on the pen object?
Dispose, when is it called?
关于IDisposable实施的最佳实践,请参阅我的博客文章:
How do you properly implement the IDisposable pattern?
Implement IDisposable if the object owns any unmanaged objects or any managed disposable objects
如果对象使用非托管资源,则应实现
IDisposable
. 拥有一次性对象的对象应实现IDisposable
以确保释放基础非托管资源 . 如果遵循规则/约定,则可以合理地得出结论:不处理托管的一次性对象等于不释放非托管资源 .