5

Let's say I have some component like this:

class SomeForm : Form
{
    private Control example;

    public void Stuff()
    {
        this.example = new ComboBox();
        // ...
        this.Controls.Add(example);
    }

    public void OtherStuff()
    {
        this.Controls.Remove(example);
    }
}

Who is responsible for calling Dispose on the example control? Does removing it from this.Controls cause it to be disposed? Or does this leak bunches of window handles backing the controls?

(For reference, I'm asking this because I don't see where the Windows Forms Designer generates code to call Dispose on a Form's children)

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552

3 Answers3

5

Form.Dispose() will dispose of the controls within the Controls collection. So removing the control from Controls will require you to dispose of the control yourself.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
3

When the form containing this control is disposed, all controls that you stored in the Controls property will be disposed. You do not need to remove your custom control from the collection. Just make sure that the containing form is disposed.

If you remove the control from the collection, then this control will eventually fall out of scope and be illegible for garbage collection. When GC runs it will call the finalizer/destructor, which in the case of the Form class will simply call the Dispose method. This being said it is bad practice to rely on that. You should always ensure that you have deterministically (manually) called the Dispose method on classes implementing the IDisposable interface as soon as you have finished working with them.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • So you're asserting that the control will be leaked? – Servy Mar 01 '13 at 21:57
  • Absolutely not. As I said in my answer, when the containing form is disposed, all the controls inside the Controls property will be disposed. So all you need to do is ensure that the containing form instance is disposed. – Darin Dimitrov Mar 01 '13 at 21:59
  • The control **isn't** in the `Controls` collection, because he removed it from that collection before disposing of the parent. It seems you missed the entire point of the question. – Servy Mar 01 '13 at 22:00
  • @Darin: So, if one removes a control from the form, one must make sure to call `Dispose` on it? – Billy ONeal Mar 01 '13 at 22:01
  • Sorry, you are right. I missed the fact that he is removing the control from the collection. If you do not dispose the control, then it will fall out of scope meaning that he will be illegible for GC. And when the GC runs it will call the Dispose method on it. So there won't be a memory leak, but it is better practice to always clal the Dispose method on classes implementing the IDisposable interface as soon as you have finished using them and not wait for the GC to do that. – Darin Dimitrov Mar 01 '13 at 22:02
  • Not quite. [A correctly written program cannot assume finalizers will ever run.](http://blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx) The GC never calls Dispose. – Billy ONeal Mar 01 '13 at 22:05
  • @BillyONeal, the GC calls the finalizer/destructor. Take a look at what the finalizer for the Form class does. Need a hint? It calls the Dispose method. – Darin Dimitrov Mar 01 '13 at 22:05
  • @Darin: Finalizer, yes. `IDisposable` is not a finalizer. – Billy ONeal Mar 01 '13 at 22:07
  • @Darin: (And in some cases the GC can give up and abandon running finalizers) – Billy ONeal Mar 01 '13 at 22:07
  • 1
    @BillyONeal, but this is not a memory leak! A memory leak means that your program is using more and more memory until you end up with an OutOfMemoryException which is not the case at all here. The finalizer might not run if you have sufficient memory on the machine. But once your machine starts to running on low memory the GC will run calling the finalizers, so no memory will ever leak with your sample code. But as I already explained, it's better practice to dispose deterministically of classes implementing the IDisposable interface as soon as you have finished using them. – Darin Dimitrov Mar 01 '13 at 22:10
  • @Darin: I never said "memory leak". I just said "leak". In this case the leaked resource would be a window handle. – Billy ONeal Mar 01 '13 at 22:18
  • @BillyONeal, and where is it going to *leak*? Why are you worried anyway? You should call the Dispose method or simply leave the control inside the Controls property. End of story. – Darin Dimitrov Mar 01 '13 at 22:21
  • Not calling Dispose yourself _could_ leak memory. In general, the GC will call the Control's finalizer which calls Dispose. But there is also a chance that the finalizer is not executed, in which case the .NET memory is reclaimed but the unmanaged memory wasn't. See [this post](http://blog.stephencleary.com/2009/08/finalizers-at-process-exit.html). – SomeWritesReserved Mar 01 '13 at 22:21
  • @Darin: It leaks the handle. This is no different than leaking a file handle or similar. The GC is designed to collect resources it owns; HWNDs aren't owned by the GC, they're owned by the windowing subsystem. I believe there's a hard limit on the number of HWNDs at around 10,000 per process. – Billy ONeal Mar 01 '13 at 22:30
  • @BillyONeal, that's true, but once the finalizer runs it will call the Dispose method which will take care of releasing the unmanaged handles. You are correct in saying that if the GC doesn't run (because you have lots of memory available for example), more and more unmanaged handles might accumulate and you might hit limits for those handles built into the OS itself. That's the reason why you should call the Dispose method yourself in order to ensure that those resources are released as soon as you have finished working with this control. – Darin Dimitrov Mar 01 '13 at 22:36
0

Always go to the source in doubt:

Form.Dispose looks a bit like this:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
       ... lots and lots of weird optimized checks ...
       base.Dispose(disposing);

Ok...Form is a ContainerControl, so:

ContainerControl.Dispose:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        this.activeControl = null;
    }
    base.Dispose(disposing);
    this.focusedControl = null;
    this.unvalidatedControl = null;
}

Grrr*...ok, ContainerControl is a Control:

Control.Dispose:

protected override void Dispose(bool disposing)
{
    ... a whole lot of resource reclaiming/funky code ...
     ControlCollection controls = (ControlCollection) 
            this.Properties.GetObject(PropControlsCollection);
     if (controls != null)
     {
         for (int i = 0; i < controls.Count; i++)
         {
              Control control = controls[i];
              control.parent = null;
              control.Dispose();
         }
         this.Properties.SetObject(PropControlsCollection, null);
      }
      base.Dispose(disposing);

So yes; calling Dispose on a Form will dispose the controls contained therein.

JerKimball
  • 16,584
  • 3
  • 43
  • 55
  • That's not what the question asks though; it's asking if only controls in the `Controls` collection are disposed when the form is disposed, meaning if he removes the control (which he does) is he now responsible for disposing of it, or is there some other mechanism (such as disposing of a control when removing it from the collection) that prevent him from needing to dispose of it. – Servy Mar 01 '13 at 22:07
  • @Servy: I'm interested in both – Billy ONeal Mar 01 '13 at 22:09
  • @Servy How the heck did I miss that? Even simpler answer: No, popping a control out of the form (removing from `Controls`) basically detaches it from the `Form` – JerKimball Mar 01 '13 at 22:09
  • @BillyONeal Reflector costs money, dotPeek is still free (I believe), Rotor contains a LOT of the CLR codebase, and Mono (while not useful in this case) closely follows the BCL in many places. – JerKimball Mar 01 '13 at 22:10
  • 1
    It's worth noting that you can just make your own class that extends control, does some logging in the `Dispose` method that you override, and then do a bunch of experiments to see if the method gets called or not. It may not be "proof", but it's a good starting place. – Servy Mar 01 '13 at 22:11
  • FWIW, another free decompiler (which is the first thing I do whenever a question like this arises): http://ilspy.net/ – Tim M. Mar 01 '13 at 22:18
  • @TimMedora Oh, forgot that one - yeah, I've usually got Reflector + dotPeek running at all times (they handle obfuscated code in amusingly different ways) – JerKimball Mar 01 '13 at 22:19