How to Implement IDisposable

I review a lot of C# code and the one thing I see people screw up the most is IDisposable. I think part of the reason is they don’t understand why IDisposable exists. After all, isn’t C# managed?

IDisposable exists to to cleanup unmanaged resources. When I say unmanaged, I mean things like raw file handles or unmanaged memory you might get from calling into unmanaged code. All standard .NET classes are managed so most people never have to worry about this. However, if you have a class that contains another class that implements IDisposable, you should also implement IDisposable. Before we go into why, we need to understand how classes that do contain unmanaged resources are implemented.

When a class contains unmanaged resources, it needs to implement a finalizer (also known as a destructor) that frees unmanaged resources. Finalizers are called before an object is garbage collected. However, when an object has a finalizer, it requires two passes of the garbage collector to be collected instead of one. On the first pass, the GC sees that the object implements a finalizer so it adds it to the finalizer queue. A special thread dequeues and runs the finalizer for each object. Only then can the GC free the object on the second pass. So technically IDisposable is not necessary for everything to be freed properly, but it provides us with two things:

  1. A consistent way to deterministicly cleanup of unmanaged resources.
  2. Avoiding the finalizer queue so the object can be collected in a single GC pass.

The pattern we are told to implement looks like this:

public class A : IDisposable
{
  private bool disposed = false;

  public ~A()
  {
    this.Dispose(false);
  }

  public void Dispose()
  {
    this.Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (this.disposed) return;

    if (disposing)
    {
      // Cleanup managed resources. That is, call
      // Dispose on any IDisposable objects we own.
    }

    // Cleanup unamanaged resources

    this.disposed = true;
  }
}

The first thing to note is that the finalizer calls Dispose(false) and Dispose() calls Dispose(true). When false is passed, we skip cleaning up managed resources. We need to do this because when we are on the finalizer queue, the the managed objects we reference may have already been collected since the order in which objects are finalized is not deterministic.

When Dispose() is called, we pass true to Dispose(bool) so that we dispose of any IDisposable objects we own in addition to freeing any unmanaged resources we own. That is, we free any unmanaged resources we indirectly own in addition to the ones we directly own. We also call GC.SuppressFinalize(this) to tell the GC that it does not need to call the finalizer and it is safe to collect the object on the first pass.

We make Dispose(bool) protected so that a derived class can call it. If we were to define a class B that derives from A, it would implement IDisposable in a similar fashion. The only difference is it would make an additional base.Dispose(disposing) call at the end of its Dispose(bool) implementation. If we make class A sealed then we can make Dispose(bool) private.

If our class does not own any unmanaged resources and only other IDisposable objects (which is usually the case), we don’t need the finalizer or the call to GC.SuppressFinalize(this). However, we should still implement a separate protected Dispose(bool) method so that derived classes can follow the above pattern. The only case where we can eschew the separate Dispose(bool) method is if our class is both sealed and has no unmanaged resources.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s