1

我有一个中型 Winforms 应用程序 (dotNET4.0),我在其上从 flowlayoutpanel 动态添加和删除自定义控件。
根据用户的选择,这些控件的数量会有所不同。

这工作正常,除了我注意到一些内存泄漏。我通过监视任务管理器中的“用户对象”编号来检查这一点。当我将自定义控件添加到 flowlayoutpanel 时,该数字会上升,但在处理这些控件时不会再次下降。

实际上:用户对象的数量下降了很多(可以说从 100% 到 10%:所以 90% 被正确处理,剩下 10% 的内存)......

结论:处理用户控件后,一个或多个对象仍保留在内存中。我的猜测是代表或图像,但我一无所知......会不会是静态的东西?

所以我的实际问题是:我在哪里没有正确地从用户控件中释放内存,我该如何解决这个问题?你们能帮忙吗?

非常感谢提前!

这是我的用户控件:请注意,除了_Costlinereportdata对象之外
的所有内容都可以处理。 (这应该继续使用,因为它在其他部分被链接和使用)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Classes.CustomControls
{
public partial class CostLineReport : UserControl, IDisposable
{
    #region CONSTANTS
    public static int pnlWidth = 870;
    public static int pnlHeightBase = 112;
    public static int pnlHeightExtended = 415;
    public static int pnlHeightCollapsed = 30;
    public static Color ColorNeutral = Color.FromArgb(176, 196, 222);
    public static Color ColorApprove = Color.FromArgb(173, 255, 47);
    public static Color ColorDisapprove = Color.FromArgb(255, 99, 71);
    public static Image ImageApprove = Image.FromFile(@"Images\tableAdd.png");
    public static Image ImageDisapprove = Image.FromFile(@"Images\tableDelete.png");
    public static Image ImageDetail = Image.FromFile(@"Images\tableDetail.png");
    public static Image ImageCollapse = Image.FromFile(@"Images\compile-warning.png");
    public static Image ImageViewRecords = Image.FromFile(@"Images\table.png");
    public static Image ImageCalculationInclude = Image.FromFile(@"Images\add.png");
    public static Image ImageCalculationExclude = Image.FromFile(@"Images\delete.png");
    #endregion

    #region FIELDS
    private CostLineReportData _Costlinereportdata;
    private ToolTip ttpApprove;
    private ToolTip ttpDisapprove;
    private ToolTip ttpCollapse;
    private ToolTip ttpDetail;
    private ToolTip ttpViewRecords;
    private ToolTip ttpCalculation;
    #endregion

    #region CTORS
    public CostLineReport(CostLineReportData line)
    {
        InitializeComponent();
        //
        this._Costlinereportdata = line;
        //
        this.picApprove.Click += new EventHandler(Approve);
        this.picDisapprove.Click += new EventHandler(Disapprove);
        this.picDetail.Click += new EventHandler(ResizeControl);
        this.picCollapse.Click += new EventHandler(CollapseControl);
        this.picViewRecords.Click += new EventHandler(ShowRecords);
        this.picCalculation.Click += new EventHandler(SwitchCalculateState);
        //
        this.rtMainData.Text = _Costlinereportdata.Maintext;
        this.rtDetail.Text = _Costlinereportdata.Detailtext; ;
        this.lblTitle.Text = _Costlinereportdata.Title;
        //
        ttpApprove = new ToolTip();
        ttpDisapprove = new ToolTip();
        ttpCollapse = new ToolTip();
        ttpDetail = new ToolTip();
        ttpViewRecords = new ToolTip();
        ttpCalculation = new ToolTip();
        ttpApprove.SetToolTip(this.picApprove, "Approve this line");
        ttpDisapprove.SetToolTip(this.picDisapprove, "Disapprove this line");
        ttpCollapse.SetToolTip(this.picCollapse, "Collapse this line");
        ttpDetail.SetToolTip(this.picDetail, "Show detail");
        ttpViewRecords.SetToolTip(this.picViewRecords, "View associated recordset");
        ttpCalculation.SetToolTip(this.picCalculation, "Include/Exclude from calculation");
        //
        this.picApprove.Image = CostLineReport.ImageApprove;
        this.picDisapprove.Image = CostLineReport.ImageDisapprove;
        this.picDetail.Image = CostLineReport.ImageDetail;
        this.picCollapse.Image = CostLineReport.ImageCollapse;
        this.picViewRecords.Image = CostLineReport.ImageViewRecords;
        this.picCalculation.Image = CostLineReport.ImageCalculationExclude;
        //
        Recolor();
    }
    #endregion

    #region PROPERTIES
    public RichTextBox MainTextBox
    { get { return this.rtMainData; } }
    public RichTextBox DetailTextBox
    { get { return this.rtDetail; } }
    public Label TitleLabel
    { get { return this.lblTitle; } }
    public PictureBox CalculateControl
    { get { return this.picCalculation; } }
    #endregion

    #region METHODS
    private void Approve(object o, EventArgs e)
    {
        _Costlinereportdata.Approve();
        Recolor();
    }
    private void Disapprove(object o, EventArgs e)
    {
        _Costlinereportdata.Disapprove();
        Recolor();
    }
    private void ResizeControl(object o, EventArgs e)
    {
        _Costlinereportdata.SwitchSize();
        switch(_Costlinereportdata.Viewstate)
        {
            case ViewState.Base:
                this.Height = CostLineReport.pnlHeightBase;
                break;
            case ViewState.Extended:
                this.Height = CostLineReport.pnlHeightExtended;
                break;
        }
    }
    private void CollapseControl(object o, EventArgs e)
    {
        _Costlinereportdata.Collapse();
        if (_Costlinereportdata.Collapsed)
            this.Height = CostLineReport.pnlHeightCollapsed;
        else
            this.Height = CostLineReport.pnlHeightBase;
    }
    private void Recolor()
    {
        switch (_Costlinereportdata.Approvalstate)
        {
            case ApprovalState.Approved:
                foreach (Control c in pnlColorIndicator.Controls)
                {
                    if (c is PictureBox)
                        ((PictureBox)c).BackColor = CostLineReport.ColorApprove;
                }
                pnlColorIndicator.BackColor = CostLineReport.ColorApprove;
                break;
            case ApprovalState.Disapproved:
                foreach (Control c in pnlColorIndicator.Controls)
                {
                    if (c is PictureBox)
                        ((PictureBox)c).BackColor = CostLineReport.ColorDisapprove;
                }
                pnlColorIndicator.BackColor = CostLineReport.ColorDisapprove;
                break;
            case ApprovalState.Neutral:
                foreach (Control c in pnlColorIndicator.Controls)
                {
                    if (c is PictureBox)
                        ((PictureBox)c).BackColor = CostLineReport.ColorNeutral;
                }
                pnlColorIndicator.BackColor = CostLineReport.ColorNeutral;
                break;
        }
    }
    private void ShowRecords(object sender, EventArgs e)
    {
        if (this._Costlinereportdata.Costline.LocalData != null)
        {
            using (Forms.frmCostlineRecords f = new Forms.frmCostlineRecords(this._Costlinereportdata.Costline.LocalData))
            {
                f.ShowDialog();
            }
        }
        else
            MessageBox.Show("This line has no records associated to it. The detailed list cannot be shown.",
                            "Can't show form",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Information);
    }
    private void SwitchCalculateState(object sender, EventArgs e)
    {
        if (this._Costlinereportdata.Calculationstate == CalculationState.Included)
        {
            this._Costlinereportdata.Calculationstate = CalculationState.Excluded;
            this.picCalculation.Image = CostLineReport.ImageCalculationExclude;
        }
        else
        {
            this._Costlinereportdata.Calculationstate = CalculationState.Included;
            this.picCalculation.Image = CostLineReport.ImageCalculationInclude;
        }
    }
    public void SetCalculateState(CalculationState st)
    {
        switch (st)
        {
            case CalculationState.Included:
                this._Costlinereportdata.Calculationstate = CalculationState.Excluded;
                break;
            case CalculationState.Excluded:
                this._Costlinereportdata.Calculationstate = CalculationState.Included;
                break;
        }
        this.SwitchCalculateState(this, null);
    }
    #endregion

    #region INTERFACE_IMEPLEMENTS
    void IDisposable.Dispose()
    {
        this._Costlinereportdata = null;
        this.picApprove.Image.Dispose();
        this.picCalculation.Image.Dispose();
        this.picCollapse.Image.Dispose();
        this.picDetail.Image.Dispose();
        this.picDisapprove.Image.Dispose();
        this.picViewRecords.Image.Dispose();
        this.rtDetail.Dispose();
        this.rtMainData.Dispose();
        this.lblDivider.Dispose();
        this.lblDivider2.Dispose();
        this.lblDivider3.Dispose();
        this.lblDivider4.Dispose();
        this.lblTextDivider.Dispose();
        this.lblTitle.Dispose();
        this.picApprove.Dispose();
        this.picCalculation.Dispose();
        this.picCollapse.Dispose();
        this.picDetail.Dispose();
        this.picDisapprove.Dispose();
        this.picViewRecords.Dispose();
        this.pnlColorIndicator.Dispose();
        ttpApprove.Dispose();
        ttpDisapprove.Dispose();
        ttpCollapse.Dispose();
        ttpDetail.Dispose();
        ttpViewRecords.Dispose();
        ttpCalculation.Dispose();
        base.Dispose(true);
    }
    #endregion
}
}

这是我的用户控件的内容(就 winform 控件而言)

CostLineReport - System.Windows.Forms.UserControl
-lblDivider - System.Windows.Forms.Label
-lblDivider2 - System.Windows.Forms.Label
-lblDivider3 - System.Windows.Forms.Label
-lblDivider4 - System.Windows.Forms.Label
-lblTextDivider - System.Windows.Forms.Label
-lblTitle - System.Windows.Forms.Label
-lblTopDivider - System.Windows.Forms.Label
-picApprove - System.Windows.Forms.PictureBox
-picCalculation - System.Windows.Forms.PictureBox
-picCollapse - System.Windows.Forms.PictureBox
-picDetail - System.Windows.Forms.PictureBox
-picDisapprove - System.Windows.Forms.PictureBox
-picViewRecords - System.Windows.Forms.PictureBox
-pnlColorIndicator - System.Windows.Forms.Panel
-rtDetail - System.Windows.Forms.RichTextBox
-rtMaindata - System.Windows.Forms.RichTextBox

这就是我清除所有内容的 flowlayoutpanel 的方式:

while (pnlCenterRightControls.Controls.Count > 0)
{
    pnlCenterRightControls.Controls[0].Dispose();
}
GC.Collect();

这就是我添加控件的方式

我添加了这个块,因为这也为 User 控件中的一个内部控件添加了一个 Delegate(事件)方法。

foreach (Classes.CustomControls.CostLineReportData c in SelectedCostlineReports)
    {
        Classes.CustomControls.CostLineReport r = c.ToControl();
        r.CalculateControl.Click += delegate(object o, EventArgs e) { GetCostCalculation(); };
        this.pnlCenterRightControls.Controls.Add(r);
    }

编辑解决方案
对于那些感兴趣的人,我已将我的代码更新为以下内容:

从 flowlayoutpanel 中删除控件
我还在这里删除了事件的订阅者

while (pnlCenterRightControls.Controls.Count > 0)
{
    foreach (Control contr in pnlCenterRightControls.Controls)
    {
        Classes.CustomControls.CostLineReport clrp = contr as Classes.CustomControls.CostLineReport;
        clrp.CalculateControl.Click -= GetCostCalculation;
        clrp.Dispose();
    }
}

处理我的对象
(虽然不处理我的静态图像)

    new public void Dispose()
    {
        this.Dispose(true);
    }
    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        this.picApprove.Click -= Approve;
        this.picDisapprove.Click -= Disapprove;
        this.picDetail.Click -= ResizeControl;
        this.picCollapse.Click -= CollapseControl;
        this.picViewRecords.Click -= ShowRecords;
        this.picCalculation.Click -= SwitchCalculateState;
        this._Costlinereportdata = null;
        this.rtDetail.Dispose();
        this.rtMainData.Dispose();
        this.lblDivider.Dispose();
        this.lblDivider2.Dispose();
        this.lblDivider3.Dispose();
        this.lblDivider4.Dispose();
        this.lblTextDivider.Dispose();
        this.lblTitle.Dispose();
        this.lblToplDivider.Dispose();
        this.picApprove.Dispose();
        this.picCalculation.Dispose();
        this.picCollapse.Dispose();
        this.picDetail.Dispose();
        this.picDisapprove.Dispose();
        this.picViewRecords.Dispose();
        this.pnlColorIndicator.Dispose();
        ttpApprove.Dispose();
        ttpDisapprove.Dispose();
        ttpCollapse.Dispose();
        ttpDetail.Dispose();
        ttpViewRecords.Dispose();
        ttpCalculation.Dispose();
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }
4

3 回答 3

1

如果没有可用于通过探查器运行的代码,很难判断,但我注意到的一件事是你不是-=你的事件。您的事件的任何处理程序仍将包含对您的对象的引用,因此会使它们没有资格进行垃圾收集。

编辑:

有两组事件需要担心,那些您在内部控制的事件(您需要在您的 dispose 方法中手动删除)和那些引用您控制或其子项的外部事件。

我见过的任何自动方式都是错误的或效果不佳,总的来说,我发现最好手动完成。有关自动删除事件的更多信息,请参阅此答案。

如何从控件中删除所有事件处理程序

于 2013-03-29T10:40:44.430 回答
0

从看起来你应该也处理所有这些对象:

ImageApprove.Dispose;
ImageDisapprove.Dispose;
ImageDetail.Dispose;
ImageCollapse.Dispose;
ImageViewRecords.Dispose;
ImageCalculationInclude.Dispose;
ImageCalculationExclude.Dispose;
于 2013-03-29T10:41:15.717 回答
0

C# 是垃圾收集的,所以Dispose在你的代码中有这么多是没有意义的。

通常,您不必太在意设计者添加的任何内容,而只需关注您自己的代码(应该期望生成的代码是正确的)。

此外,由于UserControl已经实现protected override void Dispose(bool disposing),您应该真正覆盖该方法,而不是尝试重新实现接口。

我不明白您的代码甚至可以如何工作......因为您的代码可能Dispose在同一个对象上多次调用。我猜预定义的控件会忽略第二次调用,否则你会有异常,因为一个对象已经被释放。

设计者添加的任何控件都将被自动释放。

调用Dispose内部对象绝对是一派胡言。每个对象都应该处理自己的对象。总是。时期。

picApprove.Image.Dispose();    // BAD CODE!!!

显然,当您手动添加控件并手动连接事件处理程序时,通常必须手动删除两者。尽管您的语法可能是等效的,但我建议您在添加事件时使用相同的简短语法(除了 += 而不是 -=)。这样,更容易确保它们都匹配。采用:

r.CalculateControl.Click += GetCostCalculation;

代替:

r.CalculateControl.Click += 
    delegate(object o, EventArgs e) { GetCostCalculation(); };

为什么要为设计器添加的控件手动添加事件处理程序?我看不出在设计器中初始化部分控件和在构造函数中初始化其他部分有什么好处。如果其他人维护该代码并认为事件未正确连接,您只会增加回归错误的风险。

new public void Dispose()是另一种完全的无意义。

最后,我发现您的代码还有许多其他问题......它没有遵循许多良好的设计实践。我建议您学习 SOLID 设计模式。

一些不良做法的例子:

  • 返回内部控件的公共属性。通常不需要,因为对这些控件进行操作的代码应驻留在此类中。您也可以搜索有关 TDA 原理的信息。
  • UI 和业务代码之间的紧密耦合。
  • 函数中重复代码(DRY 原则)Recolor
  • 不遵守 .NET 约定的命名法,特别是对于事件处理函数名称。
于 2016-12-20T01:10:57.807 回答