1

私はフローレイアウトパネルからカスタムコントロールを動的に追加および削除している中規模の Winforms アプリケーション (dotNET4.0) を持っています。
ユーザーの選択に基づいて、これらのコントロールの量は異なります。

これは問題なく動作していますが、いくつかのメモリリークに気付きました。Taskmanager で 'USER Objects' の数を監視してこれをチェックします。カスタム コントロールを flowlayoutpanel に追加すると数値が上がりますが、これらを破棄しても数値が下がりません。

実際: USER オブジェクトの数は大幅に減少します (100% から 10% までとしましょう: 90% が適切に破棄され、メモリに 10% が残ります)...

結論:ユーザー コントロールを破棄した後も、1 つ以上のオブジェクトがメモリ内に残っています。私の推測では、代表者または画像ですが、私にはわかりません...静的なものでしょうか?

したがって、私の実際の質問は次のとおりです。ユーザーコントロールからメモリを適切に解放していない場所と、これを解決するにはどうすればよいですか? 助けていただけませんか?

よろしくお願いします!

これは私のユーザー コントロールです。_ 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();

これが私のコントロールを追加する方法です

このブロックを追加したのは、これにより、ユーザー コントロールの内部コントロールの 1 つにデリゲート (イベント) メソッドも追加されるためです。

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

プロファイラーを介して実行できるコードがないと判断するのは困難ですが、私が気づいたことの 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

DisposeC# はガベージ コレクトされるため、コードにこれほど多くのガベージ コレクションを含めることは意味がありません。

通常、デザイナーによって追加されたものについてはあまり気にする必要はありませんが、独自のコードによってのみ気にする必要があります (生成されたコードが正しいことを期待する必要があります)。

また、UserControlすでに実装しprotected override void Dispose(bool disposing)ているため、インターフェイスを再実装するのではなく、そのメソッドを実際にオーバーライドする必要があります。

あなたのコードがどのように機能するのか理解できません...あなたのコードはおそらくDispose同じオブジェクトで複数回呼び出すためです。オブジェクトが既に破棄されているため、事前定義されたコントロールは2番目の呼び出しを無視すると思います。

デザイナーによって追加されたすべてのコントロールは、自動的に破棄されます。

内部オブジェクトの呼び出し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 に答える