手动调用 System.GC.Collect() 更改 Windows Forms 应用程序的操作
Manually calling System.GC.Collect() changes action of Windows Forms application
我相信手动调用 System.GC.Collect() 只会影响应用程序的性能或内存使用。但在此示例中,调用 System.GC.Collect() 会更改应用程序的操作。
using System;
using System.Windows.Forms;
public class Form1 : Form
{
public Form1()
{
this.Click += Form1_Click;
}
private void Form1_Click(object sender, EventArgs e)
{
CreateMyEventHandler()(sender, e);
//System.GC.Collect();
}
private EventHandler CreateMyEventHandler()
{
return (sender, e) =>
{
var cm = new ContextMenu(new[]
{
new MenuItem("Menu item", (sender2, e2) =>
{
Console.WriteLine("Menu item is clicked!");
})
});
cm.Show(this, ((MouseEventArgs)e).Location);
};
}
}
static class Program
{
static void Main()
{
Application.Run(new Form1());
}
}
将以上代码保存到文件 "Program.cs" 和 运行 如下。
csc Program.cs
.\Program.exe
打开window如下图
单击 window 打开上下文菜单,单击菜单项将按预期将文本信息 "Menu item is clicked!" 打印到控制台。
但是,取消注释上面示例中注释掉的行:
System.GC.Collect();
应用程序的动作发生变化。单击菜单项不打印任何内容。
这出乎我的意料。那为什么会变呢?以及如何在实际应用中防止这种意外的变化?
此示例已通过 Visual Studio 2013 年确认。
没有人保留对您的上下文菜单及其菜单项的引用。
如果您强制垃圾收集器 运行,它会检测到并删除它们。
如果你不强制垃圾收集器,这些项目也会被垃圾收集,但稍后,这就是你看到打印的原因。
这是一个过早的收集问题。你可以通过添加这个 class:
来亲眼看到
class MyContextMenu : ContextMenu {
public MyContextMenu(MenuItem[] items) : base(items) { }
~MyContextMenu() {
Console.WriteLine("Oops");
}
}
并使用 MyContextMenu 而不是 ContextMenu。注意当您调用 GC.Collect().
时您如何看待 "Oops"
这有一个技术原因,ContextMenu 是一个 .NET 1.x class,它包装了 OS 中内置的本机菜单。它不是从 Control class 派生的,因此不参与控件保持活动状态的正常方式。这是通过 Winforms 管道内部的 table 来确保只要 Control 对象的本机句柄存在,它就会一直被引用。菜单 class 缺少相同类型的 table。它变得更加混乱,因为 .NET 包装器 class 消失了,但本机 Windows 菜单仍然存在,因此 似乎 可以正常运行。
回到 .NET 1.x 时代,程序员习惯于使用设计器创建上下文菜单。效果很好,它们通过 components 集合保持引用。解决此问题的方法是:
var cm = new ContextMenu(...);
this.components.Add(cm);
cm.Show(this, ((MouseEventArgs)e).Location);
尽管您现在不断添加上下文菜单,但这也不是很好。分配表单的 ContextMenu 属性 是另一种解决方法,可能是您更喜欢的方法。但是,真的最好放弃这个古老的 1.x 组件并改用 ContextMenuStrip,它没有这个问题:
var cm = new ContextMenuStrip();
cm.Items.AddRange(new[]
{
new ToolStripMenuItem("Menu item", null, (sender2, e2) =>
{
Console.WriteLine("Menu item is clicked!");
})
});
cm.Show(this, ((MouseEventArgs)e).Location);
我相信手动调用 System.GC.Collect() 只会影响应用程序的性能或内存使用。但在此示例中,调用 System.GC.Collect() 会更改应用程序的操作。
using System;
using System.Windows.Forms;
public class Form1 : Form
{
public Form1()
{
this.Click += Form1_Click;
}
private void Form1_Click(object sender, EventArgs e)
{
CreateMyEventHandler()(sender, e);
//System.GC.Collect();
}
private EventHandler CreateMyEventHandler()
{
return (sender, e) =>
{
var cm = new ContextMenu(new[]
{
new MenuItem("Menu item", (sender2, e2) =>
{
Console.WriteLine("Menu item is clicked!");
})
});
cm.Show(this, ((MouseEventArgs)e).Location);
};
}
}
static class Program
{
static void Main()
{
Application.Run(new Form1());
}
}
将以上代码保存到文件 "Program.cs" 和 运行 如下。
csc Program.cs
.\Program.exe
打开window如下图
单击 window 打开上下文菜单,单击菜单项将按预期将文本信息 "Menu item is clicked!" 打印到控制台。
但是,取消注释上面示例中注释掉的行:
System.GC.Collect();
应用程序的动作发生变化。单击菜单项不打印任何内容。
这出乎我的意料。那为什么会变呢?以及如何在实际应用中防止这种意外的变化?
此示例已通过 Visual Studio 2013 年确认。
没有人保留对您的上下文菜单及其菜单项的引用。 如果您强制垃圾收集器 运行,它会检测到并删除它们。 如果你不强制垃圾收集器,这些项目也会被垃圾收集,但稍后,这就是你看到打印的原因。
这是一个过早的收集问题。你可以通过添加这个 class:
来亲眼看到class MyContextMenu : ContextMenu {
public MyContextMenu(MenuItem[] items) : base(items) { }
~MyContextMenu() {
Console.WriteLine("Oops");
}
}
并使用 MyContextMenu 而不是 ContextMenu。注意当您调用 GC.Collect().
时您如何看待 "Oops"这有一个技术原因,ContextMenu 是一个 .NET 1.x class,它包装了 OS 中内置的本机菜单。它不是从 Control class 派生的,因此不参与控件保持活动状态的正常方式。这是通过 Winforms 管道内部的 table 来确保只要 Control 对象的本机句柄存在,它就会一直被引用。菜单 class 缺少相同类型的 table。它变得更加混乱,因为 .NET 包装器 class 消失了,但本机 Windows 菜单仍然存在,因此 似乎 可以正常运行。
回到 .NET 1.x 时代,程序员习惯于使用设计器创建上下文菜单。效果很好,它们通过 components 集合保持引用。解决此问题的方法是:
var cm = new ContextMenu(...);
this.components.Add(cm);
cm.Show(this, ((MouseEventArgs)e).Location);
尽管您现在不断添加上下文菜单,但这也不是很好。分配表单的 ContextMenu 属性 是另一种解决方法,可能是您更喜欢的方法。但是,真的最好放弃这个古老的 1.x 组件并改用 ContextMenuStrip,它没有这个问题:
var cm = new ContextMenuStrip();
cm.Items.AddRange(new[]
{
new ToolStripMenuItem("Menu item", null, (sender2, e2) =>
{
Console.WriteLine("Menu item is clicked!");
})
});
cm.Show(this, ((MouseEventArgs)e).Location);