views:

235

answers:

3

I have a WindowsForm with a panel control which I use to display my UserControls. I add controls this way:

private void AddControl(Control control)
{
    panel.Controls.Clear();
    control.Size = new Size(panel.Width - 1, panel.Height - 1);
    control.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles. Top;
    panel.Controls.Add(control);
}

..

AddControl(new ucSomeControl());

I just clicked every button that uses AddControl() and saw memory usage increased every time. I left the application running, doing nothing, for one and a half hour and the memory usage was dropped from 140mb to 138mb, just like 2mbs. Do you think this is normal or am I doing something wrong with my control adding method which I should/could improve for less memory usage?

Followup

I've created 4 versions of my application: Debug, Release, with Dispose, with manuel GC call.

With my original code

There's little difference between debug and release versions of my application when it comes to memory usage, like 5mb. The problem with these versions is that the more I click on buttons, even if I click the same button and create the same UserControl again, memory usage equally increases.

With Dispose

I've added Chris Arnold's Dispose code. Memory usage is significantly lower and although creating more and more controls still increases memory usage, now each control uses a lot less memory. This was a worthy addendum.

With manuel GC call

I've added this code after Dispose:

GC.Collect();
GC.WaitForPendingFinalizers();

Bingo! Even less memory usage than Dispose code. The best part is, even if I create new controls over and over again, the memory usage increases are very small, almost trivial.

I really liked using Dispose + GC method but every single article I've red about manuel GC calls are strongly discouraging its usage. Even if I don't have any custom finalizers/destructors I'm undecided about using it or not..

+1  A: 

That is quite normal behaviour, due to the garbage collector. Memory gets allocated immediately when the objects are created, but not immediately freed. Later, when the garbage collector runs, it notes there are some objects with no reference to them any more, so it frees the memory.

This SO post has some links in its answers providing more information about the GC.

Doc Brown
Thanks for the link. I'm aware that GC kicks in after some unknown time but I wonder if it's normal to not see any effects after this long time.
Armagan
The GC will only kick in when needed. How much memory is left free on your machine when the app gets up to 140MB? If you have a couple of gig left the GC probably won't bother.
Chris Arnold
I have 1.5GB of free memory. I never thought that GC would not bother if there's enough free memory available :)
Armagan
+2  A: 

You could try disposing the Controls collection before you clear it. Like this...

private void AddControl(Control control)
{
    foreach (IDisposable control in panel.Controls)
      control.Dispose();

    panel.Controls.Clear();
    control.Size = new Size(panel.Width - 1, panel.Height - 1);
    control.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles. Top;
    panel.Controls.Add(control);
}
Chris Arnold
Why bothering with this additional, unneeded code? The question was not *"how to avoid it?"*, but *"is this normal behaviour?"*
Doc Brown
I would argue that it's the developers' job to dispose of disposable objects as soon as they are not needed. That is normal behaviour.
Chris Arnold
It seems I wasn't clear with my question. I sure would like to know if I can improve my code for better memory usage. Sorry about that. Thanks for the code Chris, appreciate it.
Armagan
@Chris: that was true for unmanaged languages like C++ or C, but IMHO the idea behind having managed code with a GC is that you do not have to this by yourself anymore.
Doc Brown
+1: There used to be a bug in .NET where you _had_ to call Dispose on the Control objects before removing them from a Panel. I don't know whether it's been fixed or not.
Roger Lipscombe
Doc Brown
+1  A: 

You can see what's going on by using TaskMgr.exe, Processes tab. View + Select Columns and tick "USER objects". This columns tracks window handles. Note that this column's value keeps increasing as you click the button. Calling GC.Collect() doesn't make it go down. Your program will crash and burn once it reaches 10,000.

The Control class is the only class in the .NET framework I know that requires calling Dispose(). The finalizer is not enough to ensure that the window handle is released. Calling Dispose() is normally completely automatic, the parent of a control does it when it gets disposed. The ultimate parent is the Form object, it automatically disposes itself (and its child controls) when it is closed.

But this doesn't happen when you remove controls from the Controls collection yourself. You cannot use the Clear() method, you have to do it like this:

  while (panel.Controls.Count > 0) panel.Controls[0].Dispose();

The reason it works this way is that the lifetime of a window is managed by Windows, not your program. As long as the window is alive, the control wrapper should not be garbage collected. Windows Forms keeps track of window handles in an internal table. As long as Handle is valid, the table ensures that the Control class wrapper can't be garbage collected. In other words, there is always at least one reference to the Control object.

That reference is not removed from that internal table until the window gets the WM_NCDESTROY message, the last message a window procedure receives before the window handle is destroyed. Removing the control from a Controls collection is not enough to destroy the window. If you don't explicitly call Dispose(), it will turn into a "zombie", an invisible window whose Control wrapper reference you can't obtain.

Hans Passant