Wednesday, November 19, 2008

GDI+ objects should be disposed

When a Winforms-control needs to do custom painting, this is typically done by overriding the OnPaint-method like this:

   1: protected override void OnPaint(PaintEventArgs e)
   2: {
   3:     base.OnPaint(e);
   5:     Graphics g = e.Graphics;
   7:     // WARNING: this code should be avoided because it does not dispose the GDI+ objects
   8:     g.FillRectangle(new SolidBrush(Color.Black), this.ClientRectangle);
   9:     TextRenderer.DrawText(g, "Hello world!", new Font(FontFamily.GenericSansSerif, 10f), new Point(10, 10), Color.White);
  10:     g.DrawLine(new Pen(Color.White), new Point(0, 0), new Point(100,100));
  11: }

On the internet you will find lots of example code that does custom drawing like this. The problem is that every time this code is executed (and in GUI this can be a lot!), a bunch of GDI+ objects are created that are not disposed. Eventually the garbage collector will kick in and will dispose all these objects. This is to be avoided however:

  • This puts a lot of stress on the garbage collector.
  • These objects will be cleaned up in two fazes which will impact performance even more.
  • GDI+ handles are limited resources, but the garbage collector is only invoked if your application needs more memory. It is not unlikely that your code uses a lot of GDI+ handles but does not consume a lot of memory. This may mean that the garbage collector never executes, and your code may consume a huge amount of GDI+ handles.
    This issue was especially problematic before GC.AddMemoryPressure() was added in .NET 2.

The obvious solution is to dispose the GDI+ objects when they are not needed anymore:

   1: protected override void OnPaint(PaintEventArgs e)
   2: {
   3:     base.OnPaint(e);
   5:     Graphics g = e.Graphics;
   7:     // Much better: less stress on the garbage collector
   8:     using (SolidBrush brush = new SolidBrush(Color.Black))
   9:     using (Font font = new Font(FontFamily.GenericSansSerif, 10f))
  10:     using (Pen pen = new Pen(Color.White))
  11:     {
  12:         g.FillRectangle(brush, this.ClientRectangle);
  13:         TextRenderer.DrawText(g, "Hello world!", font, new Point(10, 10), Color.White);
  14:         g.DrawLine(pen, new Point(0, 0), new Point(100,100));
  15:     }
  16: }

This code is good but we can make it even better. Note that every time the code is executed, new GDI+ objects are created and disposed. In this particular case this is a waste because the objects will be identical every time. It would be better to create the objects only once, and reuse them every time.

It seems that the Winforms-designers at Microsoft also had this idea, because they already cache the commonly used brushes and pens in the Brushes and Pens classes. For the Font-object we need to do this ourselves:

   1: static readonly Font s_Font = new Font(FontFamily.GenericSansSerif, 10f);
   3: protected override void OnPaint(PaintEventArgs e)
   4: {
   5:     base.OnPaint(e);
   7:     Graphics g = e.Graphics;
   9:     g.FillRectangle(Brushes.Black, this.ClientRectangle);
  10:     TextRenderer.DrawText(g, "Hello world!", s_Font, new Point(10, 10), Color.White);
  11:     g.DrawLine(Pens.White, new Point(0, 0), new Point(100,100));
  12: }

Notice that our Font-instance is declared as static because it can be reused when there are many instances of our control.

Of course this technique can only be used if the GDI+ objects are the same every time. If they are highly dynamic then you will have to create a new object every time (this may occur if you are drawing a part of an animation). But don’t forget to dispose these objects with the using-statement.

No comments: