English 中文(简体)
为什么总是需要在具有IDisposable成员的对象上实现IDisposaable?
原标题:Why is it always necessary to implement IDisposable on an object that has an IDisposable member?

据我所知,这是一个公认的规则,如果你有一个类a,它的成员m是IDisposable,a应该实现IDisposaable,它应该在其中调用m.Dispose()。

我找不到一个令人满意的理由来解释为什么会这样。

我理解这样一条规则,即如果您有非托管资源,您应该提供一个终结器和IDisposable,这样,如果用户没有显式调用Dispose,终结器仍将在GC期间清理。

然而,有了这个规则,你似乎不需要有这个问题的规则。例如。。。

如果我有课:

class MyImage{
  private Image _img;
  ... }

约定规定我应该有MyImage:IDisposable。但是,如果Image遵循惯例并实现了终结器,而我不关心资源的及时发布,那又有什么意义呢?

更新

此处

最佳回答

But if Image has followed conventions and implemented a finalizer and I don t care about the timely release of resources, what s the point?

如果你不关心及时发布,并且你可以确保一次性对象写得正确,那么就没有了(事实上,我从来没有这样的假设,即使是微软的代码也是如此。你永远不知道什么时候不小心溜走了)。关键是你应该关心,因为你永远不知道什么时候会引起问题。考虑一个开放的数据库连接。把它挂在那里,意味着它不会在游泳池里被替换。如果你有几个请求,你可能会用完。

如果你不在乎,没有什么能说明你必须这么做。这样想,就像在非托管程序中释放变量一样。你不必这么做,但这是非常明智的。如果没有其他原因,从程序继承的人不必想知道为什么它没有得到处理,然后尝试清除它。

问题回答

但是,如果Image遵循惯例并实现了终结器,而我不关心资源的及时发布,那又有什么意义呢?

你完全错过了Dispose的要点。这与您的便利性无关。这是关于其他可能想要使用这些非托管资源的组件的便利性。除非您能保证系统中没有其他代码关心资源的及时释放,并且用户不关心资源的按时释放,否则您应该尽快释放资源。这是礼貌的做法。

在经典囚犯的困境,在一个合作者的世界里,一个孤独的叛逃者会获得巨大的好处。但在你的情况下,作为一个孤独叛逃者,你个人只会通过编写低质量的最佳实践忽略代码来节省几分钟的时间,这只是一个微小的好处。受害的是你的用户和他们使用的所有程序,而你几乎一无所获。你的代码利用了其他专业人员grams解锁文件,释放互斥锁等等。做一个好公民,为他们做同样的事。这并不难做到,而且它使整个软件生态系统变得更好。

更新:这是我的团队目前正在处理的一个现实情况的例子。

我们有一个测试实用程序。它有一个“句柄泄漏”,因为没有积极地处理一堆非托管资源;每个“任务”可能会泄漏六个句柄。当它发现禁用的测试时,它会维护一个“要做的任务”列表,等等。我们在这个列表中有一万或两万个任务,所以我们很快就会得到太多未完成的句柄——这些句柄应该是死的,并释放回操作系统中——很快,系统中与测试无关的代码都无法运行。测试代码并不在意。它工作得很好。但最终被测试的代码无法生成消息框或其他UI,整个系统要么挂起,要么崩溃。

垃圾收集器没有理由知道它需要更积极地运行终结器以更快地释放这些句柄;为什么要这样做?它的工作是管理内存你的工作是管理手柄,所以你必须做那项工作。

首先,无法保证终结器线程何时会清理对象——想想类引用sql连接的情况。除非您确保立即处理掉,否则连接将打开一段未知的时间,并且您将无法重用它。

第二,终结不是一个廉价的过程-你应该确保如果你的对象被正确地处理了,你就会调用GC。SuppressFinalize(这个)来防止终结的发生。

在“不便宜”方面进行扩展,终结器线程是一个高优先级线程。如果你给主应用程序太多的工作要做,它会占用主应用程序的资源。

编辑:好的,这是Chris Brummie关于定稿,包括为什么它很贵。(我知道我在某个地方读过很多关于它的文章)

如果你不关心资源的及时释放,那就没有意义了。如果你可以确定代码只供你使用,并且你有足够的可用内存/资源,为什么不让GC在它选择的时候接管它呢?OTOH,如果其他人正在使用你的代码并创建许多实例(例如,MyImage),除非它处理得很好,否则很难控制内存/资源的使用。

许多类都要求调用Dispose以确保正确性。例如,如果某些C#代码使用带有“finally”块的迭代器,则如果使用该迭代器创建枚举器而未进行处理,则该块中的代码将不会运行。虽然在少数情况下,确保在没有终结器的情况下清理对象是不切实际的,但在大多数情况下,依赖终结器进行正确操作或避免内存泄漏的代码都是坏代码。

如果您的代码获得了一个IDisposable对象的所有权,那么除非对象的cleass是密封的,或者您的代码通过调用构造函数(而不是工厂方法)来创建对象,否则您无法知道对象的真实类型是什么,以及是否可以安全地放弃它。微软最初可能打算放弃任何类型的对象都应该是安全的,但这是不现实的,而且认为放弃任何类型对象都应该安全是无益的。如果一个对象订阅了事件,那么允许安全放弃将需要向所有事件添加一个弱间接级别,或者向所有其他访问添加一个(非弱)间接级别。在许多情况下,要求调用方正确处置对象比增加大量开销和复杂性以允许放弃要好。

顺便说一句,请注意,即使对象试图适应放弃,它仍然可能非常昂贵。创建一个Microsoft.VisualBasic.Collection(或它的任何名称),添加一些对象,并创建和处置一百万个枚举器。没问题--执行速度很快。现在创建并放弃一百万个枚举器。除非你每隔几千个枚举器就强制一个GC,否则这是一场重大的打盹盛宴。Collection对象是为了允许放弃而编写的,但这并不意味着它没有很大的成本。

如果你正在使用的对象实现IDisposable,它会告诉你在你完成它时它有一些重要的事情要做。重要的事情可能是释放非托管资源,或者从事件中取消挂钩,这样它就不会在你认为你完成了它之后处理事件,等等。通过不调用Dispose,你是说你比原作者更了解这个对象是如何运作的。在一些微小的边缘情况下,如果您自己编写了IDisposable类,或者您知道与调用Dispose相关的bug或性能问题,那么这实际上可能是真的。一般来说,忽略一个请求您在处理完后处理它的类是不太可能的好主意。

谈到终结器——正如已经指出的,它们有成本,可以通过释放对象(如果它使用SuppressFinalize)来避免。不仅仅是运行终结器本身的成本,也不仅仅是GC收集对象之前必须等待终结器完成的成本。带有终结器的对象在被标识为未使用且需要终结的集合中仍然存在。因此,它将被推广(如果它还没有出现在第二代中的话)。这有几个连锁反应:

  • The next higher generation will be collected less frequently, so after the finalizer runs, you may be waiting a long time before the GC comes around to that generation and sweeps your object away. So it can take a lot longer to free memory.
  • This adds unnecessary pressure to the collection the object is promoted to. If it s promoted from gen 0 to gen 1, then now gen 1 will fill up earlier than it needs to.
  • This can lead to more frequent garbage collections at higher generations, which is another performance hit.
  • If the object s finalizer isn t completed by the time the GC comes around to the higher generation, the object can be promoted again. Hence in a bad case you can cause an object to be promoted from gen 0 to gen 2 without good reason.

显然,如果你只在一个物体上做这件事,就不太可能让你付出任何明显的代价。如果您将其作为一般做法来执行,因为您发现对正在使用的对象调用Dispose令人厌烦,那么它可能会导致上述所有问题。

处置就像前门上的锁。它可能是有原因的,如果你要离开大楼,你可能应该锁上门。如果把它锁起来不是个好主意,就不会有锁了。

即使你不在乎这种特殊的情况,你仍然应该遵循标准,因为在某些情况下你会在乎的。设定一个标准并始终基于特定的指导方针来遵循它要比制定一个有时被忽视的标准容易得多。随着团队的成长和产品的老化,情况尤其如此。





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...