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.


3 comments:

  1. Thanks for the useful information. I have not fixed my tabpages to dispose on removal.

    ReplyDelete
  2. You should not dispose the designer created tab pages every time when you clear these from the tab control. I tried now and it is enough to do this in form closing event handler.

    Do the following in form closing and it should be enough to the GC, and it will delete these instances too:
    1. unsubscribe from the event handlers related to the tab control and tab pages
    2. dispose and set to nothing the existing tab page and tab control instances

    I have checked with Remote performance monitor and it is OK.

    ReplyDelete
  3. Hi Gergő,

    Thanks for leaving a comment, I really appreciate it. You are correct that you don't need to (and in fact shouldn't!) dispose the tabs *every time* you remove them. I even note this at the end of my article by saying that if you think you're going to re-add the control you shouldn't dispose them until the parent form is closed/disposed itself. You're not supposed to call dispose on an object more than once (there is even a Code Analysis rule for that).

    In my particular case I was removing the tab pages when the form loaded and not using them again at any subsequent point - because of that I felt it was cleaner to dispose them when I removed them so all the code related to the removal of the tabs was in one place rather than spread about multiple event handlers. However that is more a style issue than a requirement of the langage/CLR etc.so doing it in a handler for the form closed event is fine (and in fact what I would have done if I my code may have re-added the tab page back into the tab during the forms lifetime).

    It shouldn't be necessary to set the references to null, although it doesn't hurt either.

    Thanks again for the comment.

    ReplyDelete