我如何说服我的同事不要在所有事情上实施IDisposable?

时间:2009-08-07 08:20:28

标签: c# .net memory-management idisposable

我在一个项目中工作,其中有大量的对象由一些类实例化,这些类在应用程序的生命周期内保留在内存中。每次不时抛出OutOfMemoryExceptions都会导致大量内存泄漏。似乎在实例化的对象之前超出范围之后,它们不会被垃圾收集。

我已经将问题分离为主要是关于附加到永不分离的长生命对象的事件处理程序,从而导致长生命对象仍然具有超出范围对象的引用,然后永远不会被垃圾收集。

我的同事提出的解决方案如下:在所有类中实现IDisposable,全面和Dispose方法,使对象中的所有引用都为null,并从您附加到的所有事件中分离。

我相信这是一个非常糟糕的主意。首先是因为它“过度杀戮”,因为问题可以通过修复一些问题区域来解决,其次因为IDisposable的目的是释放对象控制的任何非托管资源,而不是因为你不信任垃圾收集器。到目前为止,我的论点都被置若罔闻。我怎么能说服他们这是徒劳的?

11 个答案:

答案 0 :(得分:16)

巧合的是,我刚才在其他地方发表了这条评论:

  

对象的引用   错误保留仍然是一个   资源泄漏。这就是GC程序的原因   仍然可能有泄漏,通常是由于   观察者模式 - 观察者是   在列表而不是可观察和   永远不会脱掉它。最终,一个   只需要removeadddelete   每个new都需要IDisposable。   完全相同的编程错误,   导致完全相同的问题。一个   “资源”实际上只是一对   必须被称为的函数   与...相同的次数   相应的论点,和   “资源泄漏”是什么时候发生的   你做不到。

你说:

  

+=的目的是释放任何内容   非托管资源您的对象   对照

现在,事件上的-=IDisposable运算符实际上是一对函数,您必须使用相应的参数调用相同的次数(事件/处理程序对是相应的参数)。

因此,它们构成了资源。并且由于GC没有为您处理(或“管理”)它们,将它们视为另一种非托管资源会很有帮助。正如Jon Skeet在评论中指出的那样,非托管通常具有特定的含义,但在IDisposable的背景下,我认为扩大它以包含任何必须被“拆除”的资源之类的东西是有帮助的。被“建立起来”。

因此事件分离是使用Dispose处理的非常好的候选者。

当然,您需要在某处调用AppDomain.UnhandledException,而不需要在每个对象上实现它(只需要那些需要管理的事件关系)。

另外,请记住,如果一对对象通过一个事件连接,并且你“抛弃它们漂流”,通过丢失所有其他对象中对它们的所有引用,它们就不会保持彼此活着。 GC不使用引用计数。一旦物体(或物体岛)无法到达,它就会被收集。

您只需担心作为事件处理程序登记的对象,其中包含长时间存在的对象上的事件。例如静态事件,例如{{1}},或应用程序主窗口上的事件。

答案 1 :(得分:11)

将他们指向Joe Duffy's post about IDisposable/finalizers - 许多聪明人的智慧。

我现在发现很难看到那里的声明“当你不需要它时不要实现它” - 但除了其他任何东西之外,向他们展示正确实施它所涉及的复杂性可能会有助于劝阻他们......

不幸的是,如果人们不听,他们就不会听。试着让他们解释为什么他们认为他们需要IDisposable。他们认为垃圾收集器不起作用吗?告诉他们它有效。如果你可以说服他们说它没有好处(对于大多数类型),那么他们肯定会停止为自己添加工作......

正如Brian所说,实现IDisposable本身不会对事件问题有所帮助 - 它实际上需要通过某种方式调用。在这种情况下,终结者也不会帮助你。他们确实需要明确地执行某些事情来删除事件处理程序。

答案 2 :(得分:9)

在所有类型中实施Dispose()并不能解决您的问题。请记住Dispose() 是自动调用的,它与回收托管内存无关。为了使Dispose()方法产生任何影响,您需要在所有相关位置调用它 - 明确地或通过using

换句话说,只是实现IDisposable并不会神奇地解决您的问题,因为除非您也更改代码中每种类型的用法,否则不会调用Dispose()方法。

但是,我建议在所有类型上实施IDisposable,因为它没有任何意义。该接口用于指示所讨论的类型使用某些资源,该资源不由垃圾收集器处理。

事件引用由垃圾收集器处理。如果您的发布商的续航时间明显长于订阅者,则只需取消订阅即可。一旦发布者去世,订阅者也将死亡。

答案 3 :(得分:5)

我曾经帮助一位同事解决了类似的问题,发生了OutOfMemoryException错误(由于事件导致对象引用挂起)。我做的第一件事是通过FXCop运行代码,突出显示Dispose没有在IDisposable类上调用。

修改要处置的代码修复了问题。也许你应该推荐使用FXCop?

也许在源存储库中找到带有问题的代码,在其上运行FXCop-看看它是否突出显示问题(如果它是由.NET Framework类引起的话可能会发生)并使用它来说服你的同事。 / p>

答案 4 :(得分:2)

这是一个艰难的过程,但我发现让人们做你想做的事情的最好方法是让他们认为这是他们的想法。你比他更了解它们,但是像“如果只有一种方法可以找出物体悬挂这么长时间的原因”和“我希望我能更多地了解持有物体的事件”这样的短语可能是一个起点。

答案 5 :(得分:2)

询问他们是否愿意在使用自行车后强行关闭电机,即使没有电机。

或者,如果他们希望在离开工作场所之前被迫按下椅子,桌子,咖啡杯和其他东西上的“关闭”按钮,即使没有任何东西可以关闭。

实现IDisposable会强制用户在不再使用对象时明确告诉对象。如果这个对象不需要清理任何东西,那只是一种不必要的复杂性。

顺便说一下,IMHO通过实现IDisposable来注销事件是一种清理事件的合适方式。有一些东西可以“关闭”。

答案 6 :(得分:1)

IDisposable仅用于释放非托管资源(SafeHandles等),Dispose方法通过类heirarchy传播。它并不意味着试图解决糟糕的非托管编程实践。

答案 7 :(得分:1)

创建解决另外一个OutOfMemoryException的职责。当有人解决属于他人的错误时,它会对自己的代码负责。 在我们项目的早期阶段,我们被推荐为“傻瓜的旗帜” - 对致命错误负责的人会在一天内获得这个标志。

答案 8 :(得分:1)

提出一个比他们的解决方案更优越的解决方案; - )

例如,一个简单的替代方法可能是在长生命对象中使用WeakReference来保存对事件处理程序的引用。这将要求事件处理程序在其他地方被引用,只要它们是需要的,但是一旦它们超出范围,它们就会被垃圾收集并且弱引用可以从长寿命对象中移除。

答案 9 :(得分:0)

通常,如果使用事件注册处理程序,那么“取消注册”就是每个对象在被销毁时应该进行的基本清理。如果你没有语言中的析构函数,那么你必须定义一个方法来调用它来告诉它正在消失的对象。该方法应该清理事件处理程序。

这不是IDisposable的用途吗?如果它已经存在,为什么还需要另一种解决方案?为什么那些傻瓜首先没有正确地实现它们的物体? ;)

答案 10 :(得分:0)

一个对象需要实现IDisposable,如果它需要确保外部的东西可能比它在被放弃之前被清除。某些对象的属性与其他对象“连接”,因此更改这些属性将更改其他对象;有时需要将这些属性设置为null。在vb.net中,“WithEvents”字段实际上是一个附加和分离事件处理程序的属性,因此vb.net中的WithEvents字段应设置为Nothing。请注意,对象实现IDisposable通常纯粹是为了使其自己的字段无效。在某些情况下,它可能会有所帮助(例如,如果一个已经存在很长时间的对象持有对最近创建的对象的引用,清除引用可能允许比它最早创建的对象更早收集否则)但肯定没必要。

有必要确保需要清理其他对象的对象实现IDisposable并确保清理其他对象。我很高兴微软鼓励人们编写放弃事件处理程序的代码。虽然在事件发布者不会超过订阅者的情况下,人们可以放弃放弃事件处理程序,而且这种情况通常发生在相互关联的GUI元素中,但我真的没有看到任何理由事件不应该总是当然是清理干净了。