【问题标题】:Unsubscribing from an event退订活动
【发布时间】:2011-01-07 16:22:40
【问题描述】:

我有以下功能。

它的作用是,给定一个控件(很可能是一个 Windows 窗体),我希望所有控件包含“遵守”规则(筛选我想要的控件的功能)订阅到一个事件(比如说 KeyDown)。

问题是:我如何退订?或者更重要的是,我是否需要?

由于我将在表单本身的表单加载事件中使用这个,如果表单关闭,我真的需要取消订阅吗?

(经过一些简单的阅读和对 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,所以如果我做了一些非常愚蠢的事情,请告诉我。

【问题讨论】:

    标签: c# events anonymous-function


    【解决方案1】:

    首先,我认为您不能取消订阅匿名函数,除非它被分配给处理程序变量并且该变量被添加到事件中然后从事件中删除。

    是否需要取消订阅:考虑对象的生命周期。您在静态方法中创建匿名函数并将其附加到我假设您控制其生命周期的控件。

    当您处置这些控件之一时,它们将不再引用匿名函数并且 GC 可以杀死它们(匿名函数),因此您不需要 取消订阅。 p>

    如果情况颠倒过来,并且在静态方法中创建的东西引用了控件,就像在静态上下文中将控件委托添加到事件一样,那么 GC 在引用之前无法处理控件到他们被删除,如果它是在静态方法中完成就不会发生。

    【讨论】:

    • “首先,我认为您不能取消订阅匿名函数,除非它被分配给处理程序变量并且该变量被添加到事件中然后从事件中删除。”我也读过,如果有必要取消订阅,那么这就是我要走的路。如果我在 Windows 窗体上使用它,规则是只有 TextBoxes 获取处理程序,那么:当窗口关闭时,文本框被释放,然后 GC 可以释放匿名函数。对吗?
    • 经过更多研究和 Grzenio 的有用回答后,我认为您的回答是正确的。感谢您的帮助。
    【解决方案2】:

    如果您只创建一次表单,并且这些处理程序在开始时也创建一次,那么您实际上不需要清理任何东西。

    如果您多次创建它(例如,当用户单击一个按钮时您多次创建表单),那么您需要小心。这里的答案取决于处理程序中的确切内容:

    c.KeyDown += (s, e) =>
                {
                  // do something
                };
    

    通常,从 GC 的角度来看,将委托分配给事件会导致依赖循环,例如假设一个Form包含控件A,并在A上注册了一个事件。那么直到A被释放后,表单才能被释放,而A直到表单被释放后才能被释放(因为它通过回调间接引用了表单)。如果您只与控件 A 一起创建表单,那么它可以(GC 将同时摆脱两者),但是当您动态创建控件 A 时,您可能会导致内存泄漏。

    【讨论】:

    【解决方案3】:

    您可以使用取消订阅事件

    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
    

    调用 ClassB 的订阅方法,尽管 b 已被释放。

    【讨论】:

    • 'b' 对象仍然存在,a 在其事件处理程序列表中有对 b 的引用。这就是为什么取消订阅不再需要的事件非常重要的原因。在这种情况下,您的 ClassB 可以实现/覆盖 .Dispose,并在那里取消订阅其处理程序。
    猜你喜欢
    • 2018-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多