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

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

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

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

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

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

对错误保留的对象的引用仍然是资源泄漏。 这就是为什么GC程序仍然可能有泄漏的原因,通常是由于Observer模式 – 观察者在列表上而不是observable并且永远不会被取消。 最终,每次add都需要delete ,就像每个add都需要delete 。 完全相同的编程错误,导致完全相同的问题。 “资源”实际上只是一对函数,必须使用相应的参数调用相同的次数,而“资源泄漏”就是当您未能执行此操作时发生的情况。

你说:

IDisposable的目的是释放对象控制的任何非托管资源

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

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

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

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

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

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

将它们指向Joe Duffy关于IDisposable / finalizers的post – 结合许多聪明人的智慧。

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

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

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

只在所有类型中实现Dispose()不会解决您的问题。 请记住, 不会自动调用Dispose() ,它与回收托管内存无关。 为了对Dispose()方法产生任何影响,您需要在所有相关位置调用它 – 显式或通过using

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

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

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

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

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

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

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

询问他们是否希望在使用自行车后被迫关闭电机,即使没有电机也是如此。

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

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

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

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

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

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

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

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

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

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

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