我在使用C#进行工作,我的工作场所有一些代码标准。其中之一是我们连接的每个事件处理程序(例如KeyDown
)都必须在Dispose
方法中断开连接。这样做有什么好处吗?
我在使用C#进行工作,我的工作场所有一些代码标准。其中之一是我们连接的每个事件处理程序(例如KeyDown
)都必须在Dispose
方法中断开连接。这样做有什么好处吗?
除非您希望事件发布者活得比订阅者更长,否则就没有理由删除事件处理程序。
这是一个传说已久的话题。您只需要正常地思考:发行人(例如按钮)有对订阅者的引用。如果发布者和订阅者将同时有资格进行垃圾回收(这很常见)或者如果发布者将更早地有资格进行垃圾回收,那么就没有GC问题。
静态事件会导致GC问题,因为它们实际上是一个无限长寿的发布者-在可能的情况下,我会完全不建议使用静态事件。(我很少觉得它们有用。)
另一个可能存在的问题是,如果您明确要停止侦听事件,因为如果事件被触发,您的对象会出现问题(例如,它将尝试写入关闭的流)。在这种情况下,是的,您应该删除处理程序。这很可能是在您的类已经实现IDisposable
时的情况。尽管不太可能,但实现IDisposable
仅仅是为了删除事件处理程序是值得的。
也许,这个标准是作为一种防止内存泄漏的防御性实践提出的。我不能说这是一个坏标准。但是,我个人更喜欢仅在需要时断开事件处理程序。这样,我的代码看起来更加清洁简洁。
我写了一篇博客,解释了事件处理程序如何导致内存泄漏以及何时断开连接; https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16。在这里,我将总结说明以回答您的核心问题。
C#事件处理程序运算符实际上是一个引用注入器:
在C#中,+=运算符看起来非常无害,许多新开发人员并不明白右侧对象实际上正在将其引用传递给左侧对象。
事件发布者保护事件订阅者:
那么,如果一个对象引用了另一个对象,问题是什么?问题在于,当垃圾回收器来清理并找到一个重要的保留在内存中的对象时,它不会清理所有由该重要对象引用的对象。让我简单解释一下。假设您有一个名为“Customer”的对象。假设此客户对象引用了CustomerRepository对象,以便客户对象可以搜索存储库中的所有地址对象。因此,如果垃圾回收器发现需要保持客户对象处于活动状态,则垃圾回收器还将保持客户存储库处于活动状态,因为客户对象引用了客户存储库对象。这是有道理的,因为客户对象需要customeRepository对象才能正常工作。
但是,事件发布者对象需要事件处理程序来运行吗?不需要,对吧?事件发布者独立于事件订阅者。事件发布者不应关心事件订阅者是否存活。当您使用+=运算符订阅事件发布者的事件时,事件发布者接收事件订阅者的引用。垃圾回收器认为,事件发布者需要事件订阅者对象才能正常工作,因此它不会收集事件订阅者对象。
以这种方式,事件发布者对象“a”保护事件订阅者对象“b”不被垃圾回收器收集。
只要事件发布者对象处于活动状态,事件发布者对象就会保护事件订阅者对象。
因此,如果您分离事件处理程序,则事件发布者不会持有事件订阅者的引用,垃圾回收器可以自由地收集事件订阅者。
但是,您真的需要始终分离事件处理程序吗?答案是否定的。因为许多事件订阅者确实应该在事件发布者存在的时间内保留在内存中。
一个流程图以做出正确的决策:
大部分情况下,我们发现事件订阅者对象和事件发布者对象同样重要,并且两者应该同时存在。 一个无需担心的场景示例: 例如,窗口的按钮点击事件。 在这里,事件发布者是Button,事件订阅者是MainWindow。应用该流程图,问一个问题,Main Window(事件订阅者)应该在Button(事件发布者)之前死亡吗?显然不是。对吧?那甚至没有意义。那么,为什么要担心分离单击事件处理程序呢?而且,子窗口订阅了主窗口的一个事件。
当用户在MainWindow中点击按钮时,子窗口会显示出来。然后,当用户完成来自子窗口的任务时,关闭子窗口。现在,根据我提供的流程图,如果你问一个问题:“事件订阅者(子窗口)是否应该在事件发布者(主窗口)之前死亡?”答案应该是“是”。那么,在子窗口的任务完成后,确保分离事件处理程序。好的地方是ChildWindow的Unloaded事件。然后,在子窗口的Unloaded事件中,我已经分离了事件处理程序,就像这样:
然后,我再次进行了分析,这一次,哇!不再有那个事件处理程序引起的内存泄漏。
请注意,在我的情况下,发布者和订阅者都在同一个类中,并且这些处理程序不是静态的。如何订阅和取消订阅事件
...省略... 取消订阅 为了防止事件被触发后调用事件处理程序,应该取消订阅事件。为了避免资源泄漏,您应该在订阅对象被处理之前取消订阅事件。只要未取消订阅事件,在发布对象中支持事件的多播委托就会引用封装订阅者的事件处理程序的委托。只要发布对象保留着该引用,垃圾回收器就不会删除您的订阅对象。