这是一个WinForm MDI应用程序问题(.net framework 3.0)。它将在C#中描述。对不起,它有点长,因为我尽量让事情变得清晰。
我有一个MDI应用程序。在某些时候,我发现一个MDI子表单永远不会被释放。有一个菜单可以创建MDI子窗体并显示它。当MDI子窗体关闭时,它应该被销毁,并且它所占用的内存应该被返回到.net。但令我惊讶的是,事实并非如此。所有MDI子表单实例都保存在内存中。这显然是“内存泄漏”。嗯,这不是.net的真正泄漏。只是我认为封闭的形式应该已经死了,但不知何故,至少有一个来自外部世界的未知参考文献仍然与封闭形式相关联。
我在网上看了一些文章。有人说当MDI子窗体关闭时,我应该取消所有事件处理程序,否则一些事件处理程序可能会使我的表单保持活动状态。有人说应该在表单关闭之前清理DataBindings,否则DataBindings会添加对某些全局Hashtable的引用,从而使我的表单保持活动状态。
我的表格包含很多东西。许多事件处理程序和许多DataBindings以及许多可疑控件包含用户控件和HelpProvider。我创建了一个大方法,从所有相关控件中取消所有事件处理程序,清除所有DataBindings和DataSources。帮助提供者和用户控件都是小心处理的。
最后,我发现,我不必清除DataBindings和DataSources。事件处理程序肯定会导致问题。 MDI表单结构也有助于实现某些目标。
在我的实验中,我发现,如果你创建一个MDI子表单,即使你关闭它,内存中仍然会有一个实例。引用来自主表单的PropertyStore。这意味着,除非主窗体关闭(应用程序结束),否则内存中总会有一个MDI子窗体实例。好消息是,无论您打开和关闭子表单多少次,都只会有一个实例,而不是大的“泄漏”。
说到事件处理程序,事情变得更加棘手。我必须解决这个问题,我表单上的所有事件处理程序都是匿名事件处理程序。这是一个示例代码:
//On MDI child form's design code...
Button btnSave = new Button();
btnSave.Click += new System.EventHandler(btnSave_Click);
其中btnSave_Click
也是MDI子表单中的方法。上述情况总是适用于各种控件和各种类型的事件。对我来说,这是一个双向循环参考。 btnSave通过事件处理程序保留MDI子表单的引用。 MDI子表单保留btnSave实例的引用。对我来说,这样的双向循环引用不应该对.net的垃圾收集器造成任何问题。这意味着我不必在处理表单时明确地取消事件:
btnSave.Click -= btnSave_Click;
但事实并非如此。对于某些事件处理程序,它们是安全的。忽略它们不会导致任何重复的实例。对于其他一些事件处理程序,它们将导致一个实例保留在内存中(与MDI表单结构类似,但这次是由挂起的事件处理程序引起的)。对于其他一些事件处理程序,它们将导致在内存中打开每个实例。我对这三种事件处理程序之间的差异感到困惑。控件以相同的方式创建,事件以相同的方式附加。有什么不同? (不要告诉我这是事件处理方法有所不同。)任何人都有这种有线方案的经验并且有答案吗?非常感谢。
现在,出于安全问题,我将不得不在处理表单时取消所有事件处理程序。这将是每个控件的类似代码的长列表。是否有使用反射以递归方式从控件中删除事件的一般方法?性能问题怎么样?
这是我故事的结局,我仍处于问题的中间。如有任何帮助,谢谢。
答案 0 :(得分:1)
您的问题可以通过here提供的解决方案解决。
答案 1 :(得分:0)
只要在子窗体中声明事件处理程序所连接的对象,就不必在放置子窗体时删除事件处理程序,但是当事件处理程序连接到的对象是在子表单外部声明,您必须在处理子表单时删除事件处理程序。
如果多次执行此代码(并且btnSave是未在子表单中声明的对象)
btnSave.Click += btnSave_Click;
此代码必须执行相同的次数
btnSave.Click -= btnSave_Click;
可能是您在应用程序中的某处引用了子表单,必须在垃圾收集器删除子表单对象之前删除此引用。