4

我试图了解如何Control取消订阅事件。假设我有一个文本框,并且我已经TextChanged使用 WinForms 设计器订阅了该事件。

TextChanged事件是在析构函数中自动取消订阅,还是我必须明确取消订阅以避免内存泄漏?Textbox

public void InitializeComponents()
{
    ...
    this.emailTextBox.TextChanged += emailTextBox_TextChanged;
    ...
}

public override void Dispose()
{
    if( disposing )
    {
        // DO I REALLY NEED THIS LINE?
        this.emailTextBox.TextChanged -= emailTextBox_TextChanged;
        if(components != null)
        {
            components.Dispose();
        }
    }
    base.Dispose( disposing );
}
4

4 回答 4

6

在这种情况下,我相信不取消订阅是可以的,因为您订阅的 TextBox 完全包含在父控件中(或者这就是我的假设。)

因此,当不存在对父控件的进一步引用时,不会有对 TextBox 的任何外部引用,因此两个对象都将符合 GC 条件。

在某些情况下,您应该取消订阅事件以防止内存泄漏,因为事件持有的引用(在它的订阅者列表中)与任何其他引用相同,并且会阻止订阅者被 GC。

当对象订阅外部对象(即不属于此对象)上的事件时,可能会发生这种情况。在这种情况下,订阅者只有在订阅的对象符合 GC 条件后才符合 GC 条件。

于 2013-06-14T12:15:43.797 回答
6

任何从寿命较长的对象订阅事件的对象都应该实现IDisposable并在它为Disposed 时取消订阅这些事件。从概念上讲,当对象被释放时,没有理由不取消订阅所有事件,因为如果一个订阅的对象的事件结果比预期的寿命长,这样做可以避免出现问题。不幸的是,.NET 中的事件体系结构没有提供方便地确保在释放对象时清除事件的机制,并且在释放对象时让代码取消订阅一堆事件可能会更难确保真正的少数事件需要清理的就是其中之一。

于 2013-06-16T16:08:06.207 回答
3

事件实际上是事件处理程序(函数委托)的列表。所以当你写这个时:

this.emailTextBox.TextChanged += emailTextBox_TextChanged;

您实际上将您的委托添加emailTextBox_TextChanged到与TextChanged事件关联的现有委托列表中。

这意味着当文本框被释放时,这个列表也会被释放,所以在这种情况下你不需要取消订阅事件,你也不会出现内存泄漏。

所以要回答你的问题,文本框析构函数中的事件并没有真正取消订阅,但你不需要明确地这样做。

取消订阅有用的唯一情况是当您不希望您的函数在执行期间再处理事件时,但我认为我实际上从未需要这样做。

于 2013-06-14T12:19:33.773 回答
2

是的,你最好退订。正如官方文档所说(在这里

为了防止资源泄漏,您应该在处置订阅者对象之前取消订阅事件。在您取消订阅某个事件之前,发布对象中该事件基础的多播委托具有对封装订阅者事件处理程序的委托的引用。只要发布对象持有该引用,垃圾回收就不会删除您的订阅者对象。

于 2019-08-12T10:09:45.547 回答