“Dispose”只能用于包含非托pipe资源的types吗?
我最近和同事讨论了实现IDisposable
的Dispose
和types的价值。
我认为在实施IDisposable
的时候,尽可能快地清理types是有价值的, 即使没有非托pipe资源可以清理 。
我的同事有不同的看法, 如果您没有任何非托pipe资源,则不需要实施IDisposable
,因为您的types最终将被垃圾收集。
我的观点是,如果你有一个ADO.NET连接,你想尽快closures,那么实现IDisposable
并using new MyThingWithAConnection()
将是有道理的。 我的同事回答说,在封面下,一个ADO.NET连接是一个非托pipe资源 。 我的答复是, 一切最终都是非托pipe资源 。
我知道推荐的一次性模式 ,如果释放托pipe和非托pipe资源,如果Dispose
被调用,但是只有通过终结器/析构函数 调用时 才是免费的非托pipe资源 (并且前段时间博客提醒消费者不恰当地使用您的IDisposabletypes )
所以,我的问题是,如果你有一个不包含非托pipe资源的types,是否值得实现IDisposable
?
IDisposable
有不同的有效用途。 一个简单的例子就是保存一个打开的文件,只要你不再需要,你需要在某个时候closures。 当然,你可以提供一个方法Close
,但是在Dispose
和using模式中using (var f = new MyFile(path)) { /*process it*/ }
会更安全。
一个更受欢迎的例子就是拥有一些其他的IDisposable
资源,这通常意味着你需要提供你自己的Dispose
来处理它们。
一般来说,一旦你想要确定性的破坏任何东西,你需要实现IDisposable
。
我和你的看法的区别在于,一旦有些资源需要确定性的销毁/释放,我就尽快实施IDisposable
,而不是必须的。 在这种情况下,依靠垃圾收集不是一种select(与你的同事的说法相反),因为它发生在不可预测的时刻,实际上根本不可能发生!
任何资源在封面之下都是非托pipe的这一事实并不意味着任何事情:开发者应该考虑“何时及如何处置这个对象”而不是“在封面下如何工作”。 底层的实现可能随时间而改变。
事实上,C#和C ++之间的主要区别之一就是没有默认的确定性破坏。 IDisposable
来弥补差距:你可以订购确定性破坏(尽pipe你不能确保客户端正在调用它;在C ++中你也不能确定客户端调用对象上的delete
)。
小增加: 确定性释放资源和尽快释放资源的区别究竟是什么? 实际上,这些是不同的(尽pipe不是完全正交的)概念。
如果资源要被确定性地释放,这意味着客户端代码应该有可能说“现在,我希望这个资源被释放”。 实际上这可能不是资源可能被释放的最早时刻:持有资源的对象可能已经从资源中获得了所需的所有资源,因此可能已经释放资源。 另一方面,对象可能会select保持(通常是非托pipe的)资源,即使在对象的Dispose
运行完毕后,只在终结器中清理它(如果资源保留时间过长也不会产生任何问题)。
所以,为了尽快释放资源,严格来说, Dispose
是不必要的:只要意识到资源不再需要,对象就可以释放资源。 然而, Dispose
是一个有用的提示,不再需要对象本身 ,所以如果合适的话,资源可能会被释放。
还有一个必要的补充:不仅是非托pipe资源需要确定性的释放! 这似乎是这个问题的答案中意见分歧的关键之一。 一个人可以有纯粹的想象力构造,可能需要被确定性地释放。
例如:访问一些共享结构的权利(认为是RW锁 ),一个巨大的内存块(想象你正在pipe理一些程序的内存),使用一些其他程序的许可证(想象你不允许同时运行一些程序的X个副本)等等。这里,被释放的对象不是非托pipe资源,而是使用/使用某些东西的权利,这是程序逻辑的一种纯粹的内部结构。
小的补充:这里是一个清单[ab]使用IDisposable
: http : //www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable 。
我认为从责任angular度考虑IDisposable
是最有帮助的。 如果一个对象知道需要在不再需要的时间和宇宙结束之间(最好是尽快),并且如果它是唯一具有信息和动力的对象,则该对象应该实现IDisposable
去做吧。 例如,打开文件的对象将有责任查看文件是否closures。 如果对象在没有closures文件的情况下简单地消失,则文件可能不会在任何合理的时间段内closures。
需要注意的是,即使只与100%pipe理对象交互的对象也可以完成需要清理的事情(并且应该使用IDisposable
)。 例如,附加到集合的“已修改”事件的IEnumerator
需要在不再需要时自行分离。 否则,除非枚举器使用一些复杂的技巧,否则只要集合在范围内,枚举器就不会被垃圾回收。 如果这个集合被枚举了一百万次,那么一百万个统计者将被附加到它的事件处理程序中。
请注意,有时可能使用终结器进行清理,以防出于某种原因,在没有首先调用Dispose
情况下抛弃对象。 有时候这样做很好。 有些工作非常糟糕。 例如,即使Microsoft.VisualBasic.Collection
使用终结器将枚举器从“已修改”事件中分离出来,试图枚举这样的对象数千次而没有干涉Dispose
或垃圾回收也会导致它变得非常慢 – 很多命令其幅度要比使用Dispose
正确的性能要慢。
所以,我的问题是,如果你有一个不包含非托pipe资源的types,是否值得实现IDisposable?
当某人将一个IDisposable接口放置在一个对象上时,这告诉我创build者有意在这个方法上做某些事情,或者将来他们可能打算这样做。 在这种情况下,我总是要求处置。 即使它现在没有做任何事情,也可能在将来,因为更新了一个对象而导致内存泄漏,而且在第一次编写代码时没有调用Dispose。
事实上这是一个判断的呼吁。 你不想过度实施它,因为在那一刻,为什么还要有一个垃圾收集器。 为什么不手动处理每个对象。 如果有可能需要处理非托pipe资源,那么这可能不是一个坏主意。 这完全取决于,如果唯一使用你的对象的人是你的团队中的人,你可以随时跟进他们,并说:“嘿,这需要现在使用非托pipe资源,我们必须通过代码,并确保我们已经收拾好了。“ 如果你正在发布这个为其他组织使用这是不同的。 没有简单的方法告诉所有可能已经实现这个对象的人,“嘿,你需要确定这个对象已经被处置了。” 让我告诉你,有几件事让人堕落,而不是升级第三方程序集,以发现他们改变了他们的代码,使你的应用程序已经逃离了内存问题。
我的同事回答说,在封面下,一个ADO.NET连接是一个托pipe资源。 我的答复是,一切最终都是非托pipe资源。
他是对的,现在是一个pipe理资源。 他们会改变它吗? 谁知道,但称它不会有什么伤害。 我不会尝试猜测ADO.NET团队是做什么的,所以如果他们把它放进去,那什么都不做,那没问题。 我仍然会这样称呼它,因为一行代码不会影响我的生产力。
您还遇到另一种情况。 假设你从一个方法返回一个ADO.NET连接。 您不知道ADO连接是基本对象还是派生types。 你不知道这个IDisposable的实现是否突然变得有必要。 我总是把它叫做无论如何,因为跟踪生产服务器上的内存泄漏,当它每隔4个小时就会崩溃。
虽然已经有了很好的答案,但我只是想明确一些。
实现IDisposable
有三种情况:
- 您正在直接使用非托pipe资源。 这通常涉及从必须通过不同的P / Invoke调用释放的P / Invoke调用中检索
IntPrt
或某种其他forms的句柄 - 您正在使用其他
IDisposable
对象,并且需要对其处置负责 - 你还有其他一些需要或使用它,包括
using
块的方便。
虽然我可能有点偏见,但是您应该阅读(并显示您的同事) IDisposable
上的StackOverflow Wiki 。
Dispose
应该用于有限生命周期的任何资源。 终结者应该用于任何非托pipe资源 。 任何非托pipe资源都应该有一个有限的生命周期,但是有大量的pipe理资源(如锁)也具有有限的生命周期。
请注意, 非托pipe资源可能包含标准的CLR对象,例如保存在一些静态字段中,全部都以安全模式运行,根本没有非托pipe导入。
没有简单的方法来判断一个实现IDiposable
类IDiposable
真的需要清理某些东西。 我的经验法则就是总是把Dispose
调用到我不太清楚的对象上,像一些第三方库。
不,这不仅 适用于非托pipe资源。
build议像框架调用的基本清理内置机制一样,使您可以清理所需的任何资源,但最适合自然是非托pipe资源pipe理。
如果您聚合IDisposable
s那么您应该实现接口,以便这些成员得到及时清理。 在您引用的ADO.Net连接示例中, myConn.Dispose()
如何myConn.Dispose()
?
尽pipe如此,我认为在所有情况下都是非托pipe资源是不正确的。 我也不同意你的同事。
你是对的。 托pipe数据库连接,文件,registry项,套接字等全部保留在非托pipe对象上。 这就是为什么他们实施IDisposable
。 如果您的types拥有一次性对象,则应该实现IDisposable
并将其置于Dispose
方法中。 否则,他们可能会一直活着,直到垃圾收集导致locking文件和其他意外的行为。
一切最终都是非托pipe资源。
不对。 CLR对象所使用的内存除了被框架pipe理(分配和释放)之外的所有内容。
实现IDisposable
和调用Dispose
的对象不坚持任何非托pipe资源 (直接或间接通过依赖对象)是毫无意义的 。 它不会释放这个对象的确定性,因为你不能直接释放对象的CLR内存,因为它总是只有GC
。 对象是引用types,因为值types直接在方法级别使用时,通过堆栈操作分配/释放。
现在,大家都说他们的答案是正确的。 让我来certificate我的。 据文件记载 :
Object.Finalize方法允许对象在垃圾回收回收之前尝试释放资源并执行其他清理操作。
换句话说,在Object.Finalize()
被调用之后,对象的CLR内存被释放。 [注意:如果需要,可以明确地跳过这个呼叫]
这是一个没有非托pipe资源的一次性class级:
internal class Class1 : IDisposable { public Class1() { Console.WriteLine("Construct"); } public void Dispose() { Console.WriteLine("Dispose"); } ~Class1() { Console.WriteLine("Destruct"); } }
请注意, 析构函数隐式地将inheritance链中的每个Finalize
调用到Object.Finalize()
以下是控制台应用程序的Main
方法:
static void Main(string[] args) { for (int i = 0; i < 10; i++) { Class1 obj = new Class1(); obj.Dispose(); } Console.ReadKey(); }
如果调用Dispose
是一种以确定的方式释放托pipe对象的方法,那么每个“Dispose”后面都会紧跟一个“Destruct”,对吧? 亲自看看会发生什么。 从命令行窗口运行这个应用程序是最有趣的。
注意:有一种方法可以强制GC
在当前应用程序域中收集所有尚未完成的对象,但不会收集单个特定对象。 不过,您不需要调用Dispose
来在最终化队列中拥有一个对象。 强烈build议不要强制收集,因为这可能会影响应用程序的整体性能。
编辑
有一个例外 – 国家pipe理。 如果您的对象恰好处理外部状态,则Dispose
可以处理状态更改。 即使状态不是非pipe理对象,由于IDisposable
具有特殊的处理方式,因此使用它非常方便。 示例将是安全上下文或模拟上下文。
using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate())) { // do something as SomeUser } // back to your user
这不是最好的例子,因为WindowsImpersonationContext
内部使用系统句柄,但你得到的图片。
底线是,当实施IDisposable
你需要有(或计划有)在Dispose
方法中有意义的事情。 否则,这只是浪费时间。 IDisposable
不会改变你的对象由GCpipe理的方式。
如果types引用非托pipe资源,或者持有对实现IDisposable的对象的引用,则types应该实现IDisposable。
在我的一个项目中,我有一个带有托pipe线程的类,我们将它称为线程A,线程B和一个IDisposable对象,我们称之为C.
一个用于处理C退出。 B曾经使用C来保存exception。
我的class不得不实施IDisposable和descrtuctor,以确保事情处理正确的顺序。 是的,GC可以清理我的物品,但是我的经验是有一个竞争条件,除非我pipe理我的class级清理。
简答:绝对不是。 如果您的types具有托pipe或非托pipe成员,则应实现IDisposable。
现在的细节:我已经回答了这个问题,并提供了更多关于内存pipe理的内部和关于StackOverflow的问题GC的细节。 这里仅仅是less数:
- 依靠.NET自动垃圾收集器是不好的做法吗?
- 如果我不在笔对象上调用Dispose,会发生什么情况?
- 处置,什么时候被调用?
关于实施IDisposable的最佳实践,请参阅我的博客文章:
你如何正确实施IDisposable模式?
如果对象拥有任何非托pipe对象或任何托pipe的一次性对象,则实现IDisposable
如果一个对象使用非托pipe资源,它应该实现IDisposable
。 拥有一次性对象的对象应该实现IDisposable
以确保释放底层的非托pipe资源。 如果遵守规则/惯例,则合乎逻辑地认为,不处置pipe理的一次性对象等于不释放未pipe理的资源。
根本没有必要的资源 (托pipe或不托pipe)。 通常情况下, IDisposable
只是一个简便的方法来elimnate combersome try {..} finally {..}
,只是比较:
Cursor savedCursor = Cursor.Current; try { Cursor.Current = Cursors.WaitCursor; SomeLongOperation(); } finally { Cursor.Current = savedCursor; }
同
using (new WaitCursor()) { SomeLongOperation(); }
WaitCursor
是IDisposable
适合using
:
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; } } }
您可以轻松地组合这些类:
using (new WaitCursor()) { using (new RegisterServerLongOperation("My Long DB Operation")) { SomeLongRdbmsOperation(); } SomeLongOperation(); }