Why does the last MDI child form that was closed not get garbage collected?

c#, forms, mdichild, memory-leaks, winforms

Solution

Technically, because that `Form` is the "FormerlyActiveMdiChild". This looks like a bug. Fortunately, not a very serious one.

The ability to troubleshoot uncollected objects is a good skill to have. The windbg debugger from Microsoft that comes with the Debugging Tools for Windows (http://www.microsoft.com/whdc/devtools/debugging/default.mspx) is great for this purpose. In the walkthrough below, note that I have removed a lot of the output from windbg that is not pertinent.

- Instead of creating the MDI child instance of type `Form`, subclass it as `TestChildForm` to make it easy to identify.

- Start the executable and attach windbg. Load the .NET extensions with `!loadby sos mscorwks`.

In windbg, run `!dumpheap -type TestChildForm`.

 Address       MT     Size
01e2e960 001c650c      320  

Next, run `!gcroot 01e2e960`.

ESP:3de7fc:Root:01e29a78(System.EventHandler)->
01e26504(WindowsFormsApplication1.Form1)->
01e269b8(System.Windows.Forms.PropertyStore)->
01e2ef04(System.Windows.Forms.PropertyStore+ObjectEntry[])

Next, run `!dumparray -details 01e2ef04` and search the output for `01e2e960`.

      MT    Field   Offset                 Type VT     Attr    Value Name
6797ea24  40032a3       10         System.Int16  1 instance       56 Key
6797ea24  40032a4       12         System.Int16  1 instance        1 Mask
6798061c  40032a5        0        System.Object  0 instance 01e2e960 Value1

Finally, I ran `!name2ee System.Windows.Forms.dll System.Windows.Forms.Form` followed by `!dumpclass 6604cb84` (as determined by `!name2ee`) and looked for 56.

      MT    Field   Offset                 Type VT     Attr    Value Name
67982c4c  4001e80      fd8         System.Int32  1   static       56 PropFormerlyActiveMdiChild

If you would rather use the Visual Studio debugger instead of windbg, you must first enable Properties, Debug, Enable unmanaged code debugging. Substitute `.load sos` for `.loadby sos mscorwks`.

Problem

We've had problems with memory leaks in our application. I've managed to replicate one of the problems with the following simple example: Replication setup 1) Create the following helper class which will be used to track object creation/destruction. ``` public class TestObject { public static int Count { get; set; } public TestObject() { Count++; } ~TestObject() { Count--; } } ``` 2) Create an MDI form with three buttons, the first button will create a new MDI child as follows: ``` private void ctlOpenMDI_Click(object sender, EventArgs e) { Form newForm = new Form(); newForm.MdiParent = this; newForm.Tag = new TestObject(); newForm.Show(); } ``` The second button will be used do the same, but with a non-MDI child form: ``` private void ctlOpenNonMDIForm_Click(object sender, EventArgs e) { Form newForm = new Form(); newForm.Tag = new TestObject(); newForm.Show(); } ``` The third button will be used to garbage collect and then display how many TestObject instances are live: ``` private void ctlCount_Click(object sender, EventArgs e) { GC.Collect(); GC.WaitForPendingFinalizers(); MessageBox.Show("Count: " + TestObject.Count); } ``` Replication steps 1) Click Open MDI form button, then close the MDI form, then click the count button. It will return Count: 1. The MDI child form and the object it references was not garbage collected - something must still have a reference to it. Also: Click open MDI form three times, close all 3 forms, then click the count button. It will return Count: 1. It seems as though the last closed MDI child form is not garbage collected. Counter-cases: 1) Click Open non-MDI form, close it. Then click the count button. It will return Count: 0, the form and object have been garbage collected. Workaround I can workaround this problem by doing this: ``` Form form = new Form(); form.MdiParent = this; form.Show(); form.Close(); ``` Before the garbage collection. This makes this dummy form the last closed MDI child form so that the other ones can be garbage collected - but why should I have to do this? What is going on? Also it's a bit ugly as you will get a flicker of the form opening and closing, and it seems pretty hacky too.

Original source