IDisposable接口是如何工作的?

9

我知道它被用来释放非托管资源,但是我对Dispose什么时候实际调用感到困惑。 我知道它在using块的末尾被调用,但当对象被垃圾回收时它也会被调用吗?


抱歉,马克,但你可能在错误的森林里吠叫了。如果你不相信我,请查看其他答案中的参考资料。 - Pontus Gagge
1
哇,为什么有人要投票关闭呢?这可是一场精彩的辩论啊! - Mark Brittingham
我必须收回我先前的说法:微软表示Dispose/Finalize对是可行的。就个人而言,出于下面所述的原因,我不喜欢这种方法,但ktrauberman找到了一个参考文献证明我在这一点上不应该那么固执……所以我放弃。 - Mark Brittingham
4个回答

11

如果您正确实现IDisposable接口,您还应该包括一个终结器(finalizer),以便在对象上调用Dispose()方法。

如果这样做,Dispose()方法将由垃圾回收器(GC)调用。但是,总是尝试手动释放这些对象非常重要。

依靠终结器调用Dispose()方法的最大问题是它会在另一个您无法控制的线程中发生。这在某些情况下可能会带来不好的结果,包括在GC线程中发生异常并且具有已释放字段(field)检查。这也是为什么在Dispose()方法中包含GC.SuppressFinalize(this)非常重要的原因 - 一旦对象被释放,就不想再次释放它。


我不确定我们是否有分歧,Pontus;我专门为在Using块中使用创建IDisposable类。如果不是这样,那么您确实需要显式调用它以释放非托管资源、释放连接等。 - Mark Brittingham
kt - 我知道你可以从终结器中调用Dispose(),但我不认为这样明智。如果你在Using块中创建你的IDisposable类实例,你最终将会调用两次Dispose(),如果没有适当的处理,会导致令人讨厌且难以诊断的错误。 - Mark Brittingham
说实话,如果你担心可能会出错并忘记调用Dispose(),那么你应该将清理代码放在析构函数(Finalizer)中。如果在那里也可以,为什么要冒着使用IDisposable的风险呢?总之,据我所知,最佳实践是选择其中一种方式,而不是两者都用。 - Mark Brittingham
kt - 请看我在实际问题中的评论。我不得不承认你在MS建议方面是正确的。虽然我不喜欢这个建议,但当微软提出不同意见时,我不能反驳最佳实践需要采取其中之一。因此,我承认我错了。 - Mark Brittingham
Reed - 我已经删除了有关你错误的评论。请接受我的道歉。 - Mark Brittingham
显示剩余11条评论

3

Dispose方法在以下几个地方被调用:

  1. 在using块的结尾。
  2. 当显式调用时(例如在try{} finally{}中)。

建议您在使用完资源后自行调用它,以更好地管理资源。

编辑:我弄错了。垃圾回收期间不会调用Dispose。请参见此文章


在垃圾回收期间,Dispose() 方法不会自动调用。 - Brian Rasmussen
刚刚编辑过了。我在发布后进行了一些研究(其实应该在之前就做好的),我的错。 - Kyle Trauberman

2
Dispose()方法在Using块的末尾被调用,因此您可以指望Dispose操作(例如关闭数据库连接)在对象超出范围时发生。就是这么简单。
更新:Dispose调用中没有特定的非托管资源(尽管这是常见用途)。
更新2:Reed Copsey开始的线程上有一些关于了解IDisposable很有用的辩论。我强烈推荐 this article 以供想要了解更多的人参考。
简而言之,一个IDisposable类允许您通过Dispose()方法显式处理资源的释放(通常是非托管资源或数据库连接)。IDisposable类实例应该在“Using”块内创建,以确保实际调用Dispose方法。如果您未能这样做(或者在“finally”块等中明确调用它),则您的Dispose方法将不会被调用,您将孤立您想要清理的对象。在所有情况下,我都将可处置类放置在Using块中,您也应该这样做。
作为替代方案,您可以在终结器(即类的析构函数)中处理资源的清理。当类被 GC 回收时,这将自动调用。这种方法的缺点是您将不会显式控制对象何时进行清理,并且需要解决一些线程问题。因此,例如,在析构函数中出现的问题非常难以调试,因为它们是异步调用并在不同的线程中执行的。唯一的优势是您不必像使用 Dispose 一样记得调用终结器。当然,如果您始终使用 Using 块,这就不是问题。
注意:我(作者)和 Reed、Pontus 指出了如何避免我所说的问题。这是我的做法,但我不能争论这是唯一的做法。事实上,Microsoft 在某些情况下甚至建议从终结器中调用 Dispose()。然而,我将保留以下讨论,仅作为说明为什么要遵循 Reed 关于小心混合 Destructors 和 Dispose() 的建议的重要性。
我对Reed的答案持异议的地方在于,他声称你应该通过在Finalizer中调用Dispose()来实现一个IDisposable类。这只是混淆了两个不同的构造,并且可能会导致问题。例如,如果您在Using块中创建了IDisposable类实例,并在析构函数中调用Dispose(),它将被调用两次,可能会导致令人讨厌且难以调试的崩溃(再次强调-您无法控制GC的时间)。 (副注:这实际上适用于Destructors - 在某些情况下,它们可能会被调用多次!)

马克,你提供的链接文章与你的观点相矛盾。它在第三页上说:"Microsoft建议在使用不受管理的资源时同时实现Dispose和Finalize。"而在第四页上,它还展示了一个从析构函数调用Dispose()的示例。 - Kyle Trauberman
我认为你并不完全理解GC.SupressFinalize()的作用。你需要在Dispose()方法的结尾处调用它,这样如果开发者调用了Dispose(),垃圾回收器就不会运行终结器,因此清理只会发生一次。 - Kyle Trauberman
马克,我也很感谢这次辩论。不过我认为你应该再做一些研究。只使用其中一种方法可能会导致代码在内存管理方面不太友好。 - Kyle Trauberman
Reed在他的答案中发表了一条不错的评论。"强制用户按照使用模式进行操作很困难(而且我认为也不好),而处理这两种情况则相对容易些。" - Kyle Trauberman
这就是为什么在开发软件时保持开放的心态非常重要。你永远不知道什么时候会学到意想不到的东西。 :) - Kyle Trauberman
显示剩余3条评论

2
不,当对象被垃圾回收时不会调用它。如果您想要这种行为,可以使用析构函数(finalizer)并从那里调用Dispose()
正如您所说的那样,在using块的结尾自动调用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接