Brain Spew - Neville Mehta's Blog

Wednesday, November 29, 2006

Controls.Clear() Doesnt Dispose In Win Forms!

We are currently doing memory leak testing on the project I am on that uses Windows Forms in .NET 1.1, and an interesting thing has come up. We noticed that when calling Controls.Clear() none of the controls in the collection were being disposed and hence causing handles to be leaked.
If you open Reflector and see the System.Windows.Forms.Control.ControlCollection.Clear() method, you will notice that it simply calls RemoveAt(int index) on each control in the collection.
So how do we get around this problem of controls not disposing?
Well, do we simply have to iterate through the collection and dispose each control like below to clear the controls collection?

foreach (Control control in this.Controls)
{
if (control != null)
control.Dispose();
}

NO! This wont work.
What will happen is that only about half the controls will actually be disposed.
The reason for this is that the Dispose() method removes the control from its collection and shifts all the controls down one index in the collection, and so when the foreach operator trys to get the next control, it goes to the next index which is really two infront of what it started on before disposing the previous control.
So, unfortunatley what we need to do is something like:

for (int i = 0; i < Controls.Count; i++)
{
if (Controls[0] != null)
Controls[0].Dispose();
}

Now, I dont know if this has remained the same in .NET 2 or 3 or event ASP.NET...I will leave that to you to play around with.

7 Comments:

  • Well, this is exactly the same problem I hunted down this week. Very annoying and easily overlooked.

    I have the idea that the implementation in the .NET framework is not as we would expect.

    The problem also exists in the 2.0 framework. Backwards compatibility I guess.

    By Blogger Roho, at 8:16 pm  

  • Same goes for 3.0 ;-(

    By Blogger Roho, at 8:18 pm  

  • Thanks for the explanation, just be careful with your fix.

    "i" will increase as "Controls.Count" decreases meaning that this fix will only dispose of half of the controls.

    By Anonymous Anonymous, at 8:57 pm  

  • as a fix, you can copy the controls first to an array, then iterate through the array and dispose, then call clear on the original Controls container.

    By Anonymous Anonymous, at 5:45 am  

  • might also work for you this way:

    IList fsbControls = HostUC.Controls.Cast().ToList();
    IEnumerable Controls = (from Control fsbControl
    in fsbControls
    where fsbControl.Name.Contains("FSB_") select fsbControl);
    foreach (Control Ctrl in Controls)
    {
    HostForm.Controls.Remove(Ctrl);
    Ctrl.Dispose();
    }

    By Anonymous Anonymous, at 1:59 am  

  • I had the exact same problem tonight - this code doesn't reliably work:

    Me.Controls.Clear()

    and it really REALLY should!!!

    Had to replace with:

    For ctr As Integer = 0 To Me.Controls.Count - 1
    If Not Me.Controls(0) Is Nothing Then
    Controls(0).Dispose()
    End If
    Next

    Stupid. Thanks for the post, even 4 years later it is still handy :-)

    By Blogger Brett, at 11:20 am  

  • I used while

    While PanelMain.Controls.Count > 0 Me.PanelMain.Controls(0).Dispose()
    End While

    By Anonymous Anonymous, at 7:00 am  

Post a Comment

Links to this post:

Create a Link

<< Home