This problem is probably caused by a newly introduced bug (in .NET Framework 3.5). I recently had a user report about a similar issue, and I found out that a change in the code that manages MDI children caused a closed MDI form to be kept in memory.
When an MDI form is deactivated, a reference to the form is stored in the FormerlyActiveMdiChild property of the MDI parent form. This property is used to update the icon of the deactivated form. Below is the code used (internally by .NET) to update the icon:
- Code: Select all
if( this.FormerlyActiveMdiChild != null )
{
this.FormerlyActiveMdiChild.UpdateWindowIcon(true);
this.FormerlyActiveMdiChild = null;
}
It seems like Microsoft tried to fix a problem with the icon update while the deactivated form was actually closing, so in .NET Framework 3.5, the code now looks like this:
- Code: Select all
if( this.FormerlyActiveMdiChild != null &&
!this.FormerlyActiveMdiChild.IsClosing )
{
this.FormerlyActiveMdiChild.UpdateWindowIcon(true);
this.FormerlyActiveMdiChild = null;
}
However, this causes the assignment “this.FormerlyActiveMdiChild = null” to be skipped and a reference is left to the closed (and possibly disposed) MDI form. This prevents it (and all its child instances) from being GCed.
It would probably be more correct if the original code was changed to:
- Code: Select all
if( this.FormerlyActiveMdiChild != null )
{
if( !this.FormerlyActiveMdiChild.IsClosing )
this.FormerlyActiveMdiChild.UpdateWindowIcon(true);
this.FormerlyActiveMdiChild = null;
}
Note that this memory leak only causes a single form (and its child instances) to be kept in memory. If you open and close another form, the original form will become eligible for GC (i.e. the memory leak doesn’t grow over time). Nonetheless, it is preferable to make sure that all unused instances are correctly GCed. One workaround to this problem might be to use reflection to set the private property “FormerlyActiveMdiChild” to null, for instance in the MdiChildActivate event in the MDI parent. Something like this:
- Code: Select all
protected override void OnMdiChildActivate( EventArgs e )
{
base.OnMdiChildActivate( e );
try
{
typeof( Form ).InvokeMember( "FormerlyActiveMdiChild",
BindingFlags.Instance | BindingFlags.SetProperty |
BindingFlags.NonPublic, null,
this, new object[] { null } );
}
catch( Exception )
{
// Something went wrong. Maybe we don't have enough permissions
// to perform this or the "FormerlyActiveMdiChild" property
// no longer exists.
}
}