一、问题描述
在一个Form里添加一个ContextMenuStrip。如果在运行的时候反复打开然后关闭该Form,我们发现程序中出现内存泄露。
二、问题重现
1. 在Visual Studio中创建一个Winforms项目;
2. 在项目中添加一个Form(Form2);
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. 在WinDbg中Attach该程序,用如下命令统计UserPreferenceChangedEventHandler的数量:
!dumpheap -stat -type Microsoft.Win32.UserPreferenceChangedEventHandler
13. 重复第11、12步,我们会注意到没多重复一次,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();
}