Wednesday 9 February 2011

Drawing in (C#) .net programs: Dispose()

It is common to see warnings that dispose() should always be called on certain System.Drawing objects (Graphics, Brush, Pen, etc.) when you have finished with them. The reason for this is that these objects use (non-managed) GDI handles. If they are not collected by the GC in a timely fashion, the OS might run out of these handles, which are apparently in limited supply. I’ve never seen anyone explain what would happen in this case, or seen any problems that have been traced to this. Presumably in the vast majority of cases, the GC kicks in way before this scarce resource is used up.

However, that is not to say that it is a fruitless exercise; on the contrary, I’m sure that it would not be commented on in MSDN literature if it were not a problem, but it’s not always made clear (especially in brief articles) when one shouldn’t call dispose(). The rule is really quite obvious: if you didn't call new on it, you shouldn't call dispose() on it.

First of all, a lot of drawing is naturally done in the OnPaint() method of controls. Helpfully, you are passed a Graphics object as part of the PaintEventArgs object that accompanies the Paint event. Don’t dispose() of this! It is passed along the chain of subscribers to the Paint event, so disposal can obviously only be done at the end of this process, not in any of the OnPaint overrides or other event subscribers.

As a side note, it is usually suggested that override OnPaint() should call base.OnPaint(). There is a note on one of the MSDN pages, however, that explains that if the current class is able to take responsibility for the whole of the painting process, this is not necessary. The context is that of a class derived directly from Control, so do be careful – someone may have done something in a base class other than Control which affects the object’s state outside the drawing domain.

Dispose() should also be called on objects of type Pen and Brush. Again, this only applies to objects that you create. In particular, Brushes.DarkGreen, for example (and the similar set of other brushes and pens in the Pens static class) should not “normally” be disposed (quote from MSDN). You can use these to instantiate new objects. In this case, of course, the new object will need to be disposed when you have finished with it.

Finally, it is worth noting that the best way to call dispose() is to use the using() statement instead. Any exceptions before the dispose() call is made (either in the main code or in a finally{…} block, if the call is there) will cause the dispose() call to be missed. Using() creates a finally{…} block behind the scenes but because it is hidden, no potentially erroneous code can be introduced into it.

Incidentally, remember that using() can look after more than one object. Two objects of the same type can be declared in the same using() statement separated by commas:
using (Brush a, b)
{
    …
}

Nested using()s will work for objects of differing types (you may think they don't look nested, but the second is effectively the code block for the first):
using (Brush a)
using (Pen b)
{
   …
}

No comments:

Post a Comment