取消订阅事件

4
我有以下函数。
它的作用是给定一个控件(很可能是窗体),我希望所有符合规则(筛选我想要的控件的函数)的包含在内的控件订阅一个事件(比如说按键按下事件)。
问题是:我该如何取消订阅?或者更重要的是,我需要吗?
因为我将在窗体的Load事件上使用这个函数,如果窗体关闭了,我真的需要取消订阅吗?
(经过一些简单的阅读和对GC的一些了解后,我怀疑我不需要取消订阅,但我不确定)
//an example of using the function
    private void Form1_Load(object sender, EventArgs e)
    {
        MyEventHandler.CreateKeyDownEventHandlers(this);
    }

//the function
    public static void CreateEventHandlers(Control Ctrl)
    {
        foreach (Control c in Ctrl.Controls)
        {
            //bool Rules(Control) a function that determines to what controls'
            //events to apply the handler 
            if ( Rules(c) )
            {
                c.KeyDown += (s, e) =>
                {
                  // do something
                };

            }

            //a control might be a groupbox so we want their contained
            //controls also
            if (c.Controls != null)
            {
                if (c.Controls.Count > 0)
                {
                    CreateEventHandlers(c);
                }
            }

        }
    }

这是我第一次尝试使用事件、委托、匿名函数和Lambda表达式,如果我做了什么很蠢的事情,请告诉我。

3个回答

2
首先,我认为你不能取消订阅匿名函数,除非它被赋值给一个处理程序变量,并且该变量被添加到事件中,然后再将其从事件中删除。
需要取消订阅的情况: 考虑对象生命周期。您在静态方法中创建匿名函数并将其附加到控件上,我假设您控制这些控件的生命周期。当您处置其中一个控件时,它们将不再引用匿名函数,垃圾回收器可以清理它们(匿名函数),因此您不需要取消订阅。
如果情况反过来,静态方法中创建的某些内容引用了控件,就像在静态上下文中向事件添加控件委托一样,那么垃圾回收器就无法管理这些控件,直到对它们的引用被移除,如果在静态方法中执行此操作,则不会发生。

首先,我认为你不能取消订阅匿名函数,除非它被分配给处理程序变量,并且该变量被添加到事件中,然后再从事件中删除。如果需要取消订阅,则我会采取这种方式。如果我在Windows窗体上使用它,并且只有TextBoxes获得处理程序的规则,则当窗口关闭时,文本框将被处理并且GC可以处理匿名函数。正确吗? - Thanos Papathanasiou
经过进一步的研究和Grzenio的帮助回答,我认为你的答案是正确的。感谢你的帮助。 - Thanos Papathanasiou

2
如果你只创建一次表单,同时这些处理程序也只在开始时执行一次,那么你实际上不需要清理任何东西。
但是,如果你多次创建它(例如,当用户点击按钮时多次创建表单),那么你需要小心。答案取决于处理程序中具体包含什么内容。
c.KeyDown += (s, e) =>
            {
              // do something
            };

一般来说,将代理分配给事件可能会从GC的角度引起依赖循环。例如,假设一个窗体包含控件A,并在A上注册了一个事件。那么,在A被处理之前,窗体无法被释放,而A也无法被释放(因为它通过回调间接地引用了窗体)。如果你只是同时创建窗体和控件A,那么就没问题了(GC会同时清除两者),但是当你动态创建控件A时,可能会出现内存泄漏。


如果您创建它多次,表格将会打开和关闭,我不会同时拥有多个实例,如果我正确理解您的话,这可能是问题所在。 - Thanos Papathanasiou

0

您可以使用以下方法取消订阅事件

yourobject.Yourevent-= YourSubscribedFunction;

这将从事件中取消订阅此函数。

关于您问题的第二部分:

如果包含事件的对象被销毁,则无需取消订阅。

我不确定如果订阅对象被处理会发生什么,但我的测试表明,即使对象已不存在,该函数仍然被调用。

ClassA a = new ClassA();
using (ClassB b = new ClassB()) // implements IDisposable
{
    b.SubscribeToFoo(a); // b subscribes to FooEvent of ClassA
    a.DoFoo(); // a executes FooEvent
}
GC.Collect(); // Run Garbage Collector just to be sure
a.DoFoo(); // a executes FooEvent

尽管 b 已被处理,ClassB 的订阅方法仍被调用。

“b” 对象仍然存在,a 在其事件处理程序列表中引用了 b。这就是为什么取消订阅您不再需要的事件非常重要的原因。在这种情况下,您的 ClassB 可以实现/覆盖 .Dispose,并在那里取消订阅其处理程序。 - nos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接