Thursday, April 28, 2011

Windows Forms : Beware the Tab Control


I discovered an interesting memory leak in one of my Windows Forms applications today. The cause and symptom are entirely obvious once you think about it, but it’s easy not to notice when you’re writing the code initially.

In this particular case I had a tab control with several tabs on it, and some of those tabs needed to be visible or invisible based on either the state of the application or configuration settings. Unfortunately the .NET control for Windows Forms has neither enabled or visible properties for individual tabs, so the common solution is to remove ‘invisible’ tab pages from the tab control (or not add them in the first place).

In my case the form and each tab page had been constructed and laid out with the VS designer, so all the controls and tab pages were created in the VS generated InitializeComponent method. As a result, my form start-up code then checked the state/configuration and removed the unnecessary tabs rather than just not creating them in the first place.

That’s all good… works as expected. The problem is that when I removed the tab pages I didn’t explicitly dispose them. You don’t normally dispose tab pages, or any control, since the form normally does this for you when it’s own Dispose method is called. However, the form only does this for controls it can find, via it’s Controls property (recursively through the control hierarchy). Since I’d removed the tab page from the tab control, the form never found it to dispose.

To make matters worse, some controls (in this case my own custom controls, but I believe at least the Microsoft provided DataGridView control does the same thing) connect to events on external objects, and disconnect on dispose. Since dispose was never called, they never disconnected from those event handlers and so an external reference to them survived which in turn caused the garbage collector to leave them alone. Viola, memory leak !

It is of course a well known issue in Windows Forms that whenever you dynamically add and remove controls from a parent control you must explicitly dispose them, it was just that in this case I’d written the code many years ago and either hadn’t known that myself at the time or hadn’t realised what I was doing. As a result, I ended up with a leak.

So, if you’re removing tab pages from a tab control make sure to dispose them. Just ensure you do this at the right time. If there is any possibility that you might want to re-add the control back into the tab, you either can’t dispose it until the parent control (or more likely form) is disposed. Either that or you’ll need to create and populate (with child controls) a new instance of the tab page to add back into the tab control. Otherwise, if you add back in the old disposed instance, you’ll get errors about disposed objects being accessed.