Winforms Gotcha: Form.Close() doesn't always call Dispose()

I just ran into a weird issue. During profiling I saw that controls on a form which was already closed were still reacting to events. I checked whether the Dispose() routine of the particular Form was called, but it wasn't. However, the Dispose() routine of other forms was called after it was closed, as in: immediately.

The difference between the two situations was that if I used Form.ShowDialog(parentForm), a call to Close() on the particular form didn't call Dispose. Checking the Form.Close() documentation describes this behavior:

The two conditions when a form is not disposed on Close is when (1) it is part of a multiple-document interface (MDI) application, and the form is not visible; and (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually to mark all of the form's controls for garbage collection.

I never knew that. It's easy to overlook, as opening a form with Show() will result in a call to Dispose when Close() is called. Not calling Dispose (or better: wrap the Form usage in a using block) will lead to a memory leak and worse: could lead to hard-to-find bugs because event handlers aren't cleaned up.

So just in case you use ShowDialog() or ShowDialog(form) to show modal dialogs in winforms, be aware that you've to call Dispose() yourself.

11 Comments

  • I always use modal forms with "using" statement

  • Great tip; not everyone reads the "fine print"

    Thanks for posting.

  • Aha, indeed. And if you think about it, it makes sense, specially for ShowDialog situation, since you usually want to retrieve information from it after the (user) closed it.

  • If a Form doesn't get disposed during its Close(), it doesn't mean that it's never disposed.
    Eventually, the GarbageCollector calls Dispose() on the Forms displayed with ShowDialog().
    Other handles on the form may cause memory leak issues.

  • Digging a bit deeper in the issue, I've seen that the GarbageCollector calls Dispose() on those forms displayed with ShowDialog() which don't have any references (like public methods used as event handlers for other components).
    This Dispose(), however, doesn't fire the Disposed event of the form.

  • @miha: good point, it is indeed useful, the only drawback is: it depends on the way the form is opened how Close() behaves. That's IMHO not really intuitive. What might have been better is a parameter to Close which would make the form become hidden instead of closed (or simply a Hide() method instead of a Close() method. )

    @Filini: Dispose is called when the app is closed, as the Hidden forms are kept in memory, at least in my debugging sessions I saw that happening. Often this isn't something to worry about, but if the form holds event handlers, it might be worth it to look into this.

    So indeed rule of thumb: use a using statement with modal dialogs opened with ShowDialog().

  • @FransBouma: I just set up a test app, and also used WinDbg to check the objects in memory. If you force the CG.Collect() you'll see that Dispose() is called (if the form doesn't have any reference) and the object is unloaded from memory.
    The leak (object kept in memory) is caused by strong handles on the form, not by the missing Dispose(). Even if you use "using" or force a Dispose(), if the object is referenced it will not be removed from memory.
    If you want, I can send you my test app.

  • @filini: I know. :) references to the form of course keep it in memory.

  • @frans: I agree that it isn't intuitive at all and probably a lot of devs doesn't realise this "feature". Even better rule of thumb: do dispose/using everything that implements IDisposable asap. :-)

  • @Miha: sometimes, dispose/using is not enough. If you do something like this:

    using(Form3 f3 = new Form3())
    {
    this.Resize += new EventHandler(f3.FooEvent);
    f3.ShowDialog(this);
    }

    Your Form3 is disposed, but it is never removed from memory, because its FooEvent() is used as an event handler for the main form.

    After dealing with a big application with HUGE memory leaks problems, my rule of thumb is: do using/dispose for every form AND check all over the place for f*!@ng handlers/references :P

  • Thanks for bringing that up Frans!

    I tend to go with Miha's advice ;-) plus trying to put as much as possible event handling into anonymous methodsm which won't save you completely though.

    Regards,
    André

Comments have been disabled for this content.