Winforms: ContextMenuStrip中的内存泄露

一、问题描述

在一个Form里添加一个ContextMenuStrip。如果在运行的时候反复打开然后关闭该Form,我们发现程序中出现内存泄露。

二、问题重现

1. Visual Studio中创建一个Winforms项目;

2. 在项目中添加一个FormForm2);

3. Form2中添加一个ContextMenuStrip

4. Form2添加一个MouseClick事件响应器(Event Handler);

5. Form2_MouseClick添加如下代码:

private void Form2_MouseClick(object sender, MouseEventArgs e)

{

if (e.Button == System.Windows.Forms.MouseButtons.Right)

{

contextMenuStrip1.Show(this, new Point(10, 10));

}

}

6. Form1中添加一个Button

7. Form1中为button1添加Click事件响应器;

8. button1_C lick添加如下代码:

private void button1_Click(object sender, EventArgs e)

{

Form2 form = new Form2();

form.ShowDialog();

GC.Collect();

GC.WaitForPendingFinalizers();

GC.Collect();

}

9. 编译运行;

10. Form1上点击button1,弹出Form2

11. Form2上单击右键,弹出菜单,在关闭Form2

12. WinDbgAttach该程序,用如下命令统计UserPreferenceChangedEventHandler的数量:

!dumpheap -stat -type Microsoft.Win32.UserPreferenceChangedEventHandler

13. 重复第1112步,我们会注意到没多重复一次,UserPreferenceChangedEventHandler会多一个实例。内存泄露发生了。

三、问题分析

Winforms为一个控件(Control)创建句柄的时候,会为UserPreferenceChanged注册一个事件响应器。当我们销毁该控件的句柄时,我们会删除这个事件响应器。通常一个控件的句柄在两种情况下会被销毁:一是当该控件收到WM_C LOSE消息时,二是当控件被Dispose的时候。

通常当我们关掉一个Form的时候,Form本身以及它的子控件都能收到WM_CLOSE消息,因此控件的句柄都会被销毁,同时UserPreferenceChanged的事件响应器也会被删除。但当一个ContextMenuStrip为不可见的时候,是收不到WM_CLOSE消息的,因此它的UserPreferenceChanged 事件响应器没被删除,从而导致内存泄露。

为了能销毁它的句柄并删除对应的UserPreferenceChanged事件响应器,我们应该调用它的Dispose函数。

四、解决方案

在上面的例子中,我们需要如下两步来Dispose一个ContextMenuStrip

1. Form2.Dispose调用ContextMenuStripMenu.Dispose

protected override void Dispose(bool disposing)

{

if (disposing)

{

if(components != null)

components.Dispose();

contextMenuStrip1.Dispose();

}

base.Dispose(disposing);

}

2. 当关闭Form2的时候,调用Form2.Dispose。我们可以通过using关键字来自动Dispose Form2

private void button1_Click(object sender, EventArgs e)

{

using (Form2 form = new Form2())

form.ShowDialog();

GC.Collect();

GC.WaitForPendingFinalizers();

GC.Collect();

}

你可能感兴趣的:(C++,c,windows,C#,Microsoft)