从我所知道的,如果你有一个具有IDisposable的成员m的A类,A应该实现IDisposable并且应该在其中调用m.Dispose()。这是一个公认的规则。
我找不到令人满意的理由,为什么会这样。
我理解如果你有非托管资源的规则,你应该提供一个终结器和IDisposable,这样如果用户没有显式调用Dispose,终结器仍将在GC期间清理。
但是,根据该规则,您似乎不需要具有此问题的规则。例如......
如果我有课:
class MyImage{
private Image _img;
... }
约定规定我应该MyImage : IDisposable
。但是如果Image遵循惯例并实现了终结器而且我不关心资源的及时发布,那有什么意义呢?
更新
在here找到了我想要获得的内容的一个很好的讨论。
答案 0 :(得分:22)
但是如果Image遵循惯例并实现了终结器而我不关心及时释放资源,那有什么意义呢?
你完全错过了Dispose的观点。这不是你的方便。这是关于其他组件的便利性,可能希望使用这些非托管资源。除非您能保证系统中没有其他代码关心资源的及时发布,并且用户不关心及时发布资源,否则您应该发布资源尽快。这是礼貌的事情。
在经典Prisoner's Dilemma中,合作社世界中唯一的叛逃者获益匪浅。但是在你的情况下,作为一个孤独的叛逃者只能通过编写低质量,最佳实践忽略代码来节省几分钟,从而产生微小的好处。这是你的用户和他们使用的所有程序,你几乎什么也得不到。您的代码利用了其他程序解锁文件并释放互斥锁和所有内容的事实。做一个好公民,为他们做同样的事。这并不难,它使整个软件生态系统变得更好。
更新:以下是我的团队正在处理的现实情况示例。
我们有一个测试工具。它有一个“句柄泄漏”,因为一堆非托管资源没有被积极处理;每个“任务”可能会泄漏六个手柄。它在发现禁用的测试时会维护一个“要执行的任务”列表,依此类推。我们在这个列表中有十到两万个任务,所以我们很快就会得到这么多未完成的句柄 - 句柄应该已经死了并释放回操作系统 - 很快系统中没有任何代码<与测试无关的em> not 可以运行。测试代码无关紧要。它工作得很好。但最终正在测试的代码无法制作消息框或其他UI,整个系统会挂起或崩溃。
垃圾收集器没有理由知道它需要更积极地运行终结器以更快地释放这些句柄;为什么要这样?它的工作是管理记忆。 您的工作是管理句柄,因此您必须完成这项工作。
答案 1 :(得分:6)
但是如果Image遵循惯例 并实现了终结者和我 不关心及时释放 资源,重点是什么?
然后没有一个,如果你不关心及时发布,你可以确保一次性对象被正确写入(事实上我从来没有做过这样的假设,甚至没有使用MS代码。你永远不会知道什么时候意外滑倒了)。关键是你应该关心,因为你永远不知道什么时候会引起问题。考虑一个开放的数据库连接。让它闲置,意味着它不会在池中更换。如果您有多个请求进入,则可能会用完。
如果你不在乎,没有什么说你必须这样做。可以这样想,就像在非托管程序中释放变量一样。你没必要,但这是非常明智的。如果没有其他原因,继承该计划的人不必怀疑为什么没有得到照顾,然后尝试清除它。
答案 2 :(得分:2)
首先,无法保证终结器线程何时清理对象 - 考虑类具有sql连接引用的情况。除非您确保将其迅速处理掉,否则您将在未知的时间段内打开连接 - 并且您将无法重复使用它。
其次,最终确定并不是一个便宜的过程 - 你应该确保如果你的对象被正确处理掉,你就会调用GC.SuppressFinalize(this)来阻止最终化。
扩展“非便宜”方面,终结器线程是一个高优先级的线程。如果你给它太多的话,它会占用主要应用程序的资源。
编辑:好的,这是Chris Brummie关于Finalization的博客文章,其中包括为什么价格昂贵。 (我知道我会在某处阅读有关此内容的内容)
答案 3 :(得分:1)
如果你不关心及时释放资源,那么确实没有意义。如果您可以确定代码仅供您使用,并且您有足够的可用内存/资源,那么为什么不让GC在选择时将其清理干净。 OTOH,如果其他人正在使用您的代码并创建(例如)MyImage
的许多实例,那么控制内存/资源使用将非常困难,除非它处理得很好。
答案 4 :(得分:1)
许多类要求调用Dispose以确保正确性。例如,如果某些C#代码使用带有“finally”块的迭代器,那么如果使用该迭代器创建枚举器而不处置枚举器,则该块中的代码将不会运行。虽然在某些情况下确保在没有终结器的情况下清理对象是不切实际的,但对于大多数依赖终结器进行正确操作或避免内存泄漏的代码都是错误的代码。
如果您的代码获得了IDisposable对象的所有权,那么除非对象的cleass被密封或者您的代码通过调用构造函数(而不是工厂方法)创建对象,否则您无法知道实际类型是什么对象是,是否可以安全放弃。微软最初可能原本打算放弃任何类型的对象应该是安全的,但这是不现实的,并且相信放弃任何类型的对象应该是安全的,这是无益的。如果一个对象订阅了事件,那么允许安全放弃将要求为所有事件添加一个弱间接级别,或者为所有其他访问添加一个(非弱)间接级别。在许多情况下,最好要求调用者正确地处理对象,而不是增加显着的开销和复杂性以允许放弃。
另外请注意,即使对象试图容纳放弃,它仍然可能非常昂贵。创建一个Microsoft.VisualBasic.Collection(或其他任何名称),添加一些对象,并创建和处理一百万个枚举器。没问题 - 执行得非常快。现在创建并放弃一百万个enumeartors。除非你每隔几千名调查员强制使用一次GC,否则这是一次重要的贪睡节目。编写Collection对象是为了允许放弃,但这并不意味着它没有重大成本。
答案 5 :(得分:1)
如果您正在使用的对象实现了IDisposable,它告诉您在完成它时有一些重要的事情要做。重要的事情可能是释放非托管资源,或者从事件中取消,以便在您认为完成事件后不会处理事件等等。通过不调用Dispose,您说你知道的更好关于该对象如何操作比原作者。在一些微小的边缘情况下,如果您自己编写IDisposable类,或者您知道与调用Dispose相关的错误或性能问题,这实际上可能是真的。一般来说,忽略一个要求你在完成后处理它的类是不太可能的。
谈论终结器 - 正如已经指出的那样,它们有成本,可以通过处理对象(如果它使用SuppressFinalize)来避免。不仅仅是运行终结器本身的成本,而且还不仅仅是在GC可以收集对象之前必须等到终结器完成的成本。具有终结器的对象在该集合中存活,其中该对象被识别为未使用且需要完成。因此它将被提升(如果它还没有在第2代)。这有几个影响:
显然,如果你只在一个物体上做这件事,那么你不可能花费任何明显的成本。如果你这样做是因为你发现在你使用的对象上调用Dispose,那么它可能导致上述所有问题。
处理就像锁在前门上。这可能是有原因的,如果你要离开大楼,你应该把门锁上。如果锁定它不是一个好主意,那就不会有锁定。
答案 6 :(得分:0)
即使您不关心这种特殊情况,您仍应遵循标准,因为在某些情况下您会关心。设置一个标准并且总是基于特定的指导方针,而不是有时会忽略的标准,这样做要容易得多。随着团队的发展和产品的老化,尤其如此。