8

我将制作一个 GUI,该 GUI 将动态创建一组控件,并为其分配事件。我需要在运行时添加和删除这些控件。它看起来像这样:

FlowLayoutPanel.Controls.Clear();
<< add new controls, assigning Click events with += >>

我听说使用 += 分配事件处理程序会导致内存泄漏(更具体地说,在应用程序退出之前不会释放内存)。我想避免这种情况。我知道我可以编写一些函数,例如如何从控件中删除所有事件处理程序以查找所有事件处理程序并删除它们,但它看起来非常复杂。

还有其他方法吗?调用 Dispose 是否有助于删除这些事件处理程序?您可以像在 C/C++ 中那样销毁对象以强制释放它们的内存吗?

谢谢!

PS:问题是,我不知道要分离什么事件。我将创建许多标签并向它们添加不同类型的 onclick 事件。当需要清理流布局面板时,无法知道哪个事件处理程序附加到哪个标签。

这是示例代码(_flowLP 是一个 FlowLayoutPanel) - 此 Refresh() 函数在应用程序退出之前运行多次。

    private void Refresh()
    {
        Label l;
        Random rnd = new Random();

        // What code should i add here to prevent memory leaks
        _flowLP.Controls.Clear();

        l = new Label();
        l.Text = "1";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "2";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "3";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "4";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "5";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "6";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);
    }
4

3 回答 3

3

当您将寿命较短的事件消费者附加到寿命较长的事件生产者时,这主要是一个问题。如果他们有相似的生活或与我描述的相反,这不是问题。

如果您确实担心这一点,只需使用 -= 从事件中分离。这将删除附件创建的引用,并有助于避免此类内存问题。

编辑:由于评论有点长,我会在这里发布一些跟进。当你附加到一个事件时,你所做的就是在事件提供者上挂一个对你自己的引用。因此,例如,如果您有一个带有 StrikesMidnight 事件的 Clock 类,并且您从一个名为 Bedtime 的类中订阅该事件,那么 Bedtime 的实际机制clock.StrikesMidnight += this.HandleMidnight;就是您正在为自己分配时钟引用。就好像时钟有一个对象属性,你说clock.ObjectProperty = this;

因此,在 Bedtime 类的生命周期很短并且超出范围的情况下,Bedtime 会出现,在 Clock 上挂起对自身的引用,然后超出范围。问题是,时钟仍然有它的引用,所以即使它超出范围,垃圾收集器也不会收集就寝时间。

……

这就是背景。在您的情况下,您正在创建一个标签并将对您自己的引用附加到它(通过您的“MethodX”处理程序)。调用刷新时,您会清除标签列表(意味着它们超出范围)。它们超出了范围,并且通过 MethodX 处理程序引用了您的类,但那又如何呢?他们有引用并不能阻止他们被 GC'ed。没有人在您的代码中持有对它们的引用,因此 GC 将对它们进行处理并且您不会泄漏内存。

于 2012-04-09T18:06:34.943 回答
0

只要您处理包含表单,垃圾收集器就应该清理所有控件。

订阅控件上的事件不会使控件保持活动状态,因为控件具有对处理委托的引用;委托没有对控件的引用。

事件订阅会阻止控件被清理的情况是,控件中的某些代码订阅了它所包含的表单实例之外的事件。例如,如果自定义组合框订阅了一个事件在静态类上让控件知道何时应更新其选项列表。如果自定义控件没有取消连接此事件,它将在应用程序期间被静态类事件寄存器引用。由于控件将引用其容器(等等),因此整个表单可能会保留在内存中。在这种情况下,控件应在其 Dispose 方法中取消连接事件。订阅静态类或长寿命实例类上的事件应始终引发危险信号,并在不再需要时显式取消连接。

在表单中,只要您的所有事件都连接到表单类或范围由表单实例控制的对象上的实例方法,那么当表单根的对象图退出时,控件将被清理范围。请注意,外部类在不再需要表单后不会保留对表单的引用。

于 2012-04-09T19:02:37.367 回答
0

我的第一个建议是不要预先优化。在尝试解决它之前,请确保这是一个问题。

关于 Dispose():Dispose只能用于在对象被垃圾回收之前释放非托管资源。有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

为了防止事件处理程序保留对控件的引用,您需要让控件取消订阅它们在创建时订阅的所有事件处理程序。提示:如果您通过 GUI 添加事件处理程序,请调查自动生成的代码以查看在调用之前需要取消订阅的内容FlowLayoutPanel.Controls.Clear()。一种干净(-er?)的方法是创建您自己的控件,该控件继承自所需控件并添加一个“Cleanup()”方法,该方法取消订阅任何已订阅的事件处理程序(您应该知道哪个事件处理程序已被订阅 - 因为您编写了代码或者它是为您生成的)。

于 2012-04-09T20:06:15.650 回答