Practical Programming Pearls For .NET Developers

Implementing IDisposable

Created: 19 February 2006

This article will attempt to explain why we have IDisposable, when you should implement it and how to implement it properly.

Why Do We Need It

Anyone who has worked with .NET for any length of time knows that .NET uses automatic memory management.  The garbage collector (GC) runs in the background and will free up memory as needed.  This is a non-deterministic approach meaning we have little control over when it occurs.

In general this is a good thing.  Does it really matter whether or not that memory we allocated a while back has been freed yet?  In general, no.  However some objects really must have deterministic behavior.  The canoncial example would be any class that uses unmanaged, shared resources like database connections, file handles or synchronization objects.  In these cases it is important to be able to free these objects when they are no longer needed.

Ideally the framework would detect that an object is no longer needed as soon as it occurs and automatically free up the memory.  However this would put an undo strain on the system.  Therefore for deterministic clean up it is still necessary for developers to manually free objects when they are no longer needed.  So how does a developer know when they should free an object or let .NET handle it.  Enter IDisposable.  This interface identifies a type that must be freed when it is no longer needed.  Of course there is always the chance that the user will forget to free the object so .NET still has to ensure that the object gets freed at some future point.

IDisposable

IDisposable has only a single member called Dispose.  This method is called when the object needs to be freed.  Internally .NET will always call this method when the object is freed.  However users should also call this method when the object is no longer needed.  Within this method any shared/unmanaged resources should be released.  Here is an example of file-type class'es implementation of the interface.

public class FileBase : IDisposable
{
   private IntPtr m_pUnmanagedResource;

   public void Dispose ( )
   {
      if (m_pUnmanagedResource != IntPtr.Zero)
      {
          //Free it
          m_pUnmanagedResource = IntPtr.Zero;
      };
   }
}

This implementation uses implicit interface implementation support to expose a public Dispose method that clients can call to clean up the resource.  However dispose does not necessarily mean much to people so we can easily create a separate method that internally does the clean up and then explicitly implement the interface like so.

public class FileBase : IDisposable
{
   private IntPtr m_pUnmanagedResource;

   public void Close ( )
   {
      if (m_pUnmanagedResource != IntPtr.Zero)
      {
          //Free it
          m_pUnmanagedResource = IntPtr.Zero;
      };
   }

   void IDisposable.Dispose ( )
   { Close(); }
}

Now the act of closing a file is exposed while under the hood the close method is used to dispose of the unmanaged resources.

Ensuring Clean Up

The above code handles the case where the user will remember to clean up the object when they are done but it still does not handle the case of .NET itself cleaning up the object.  In order to run code when an object is to be freed we need to define a finalizer for the class.  A finalizer actually delays the process of freeing the object but it allows us to execute some code in the process.  Normally when we implement IDisposable we will also implement a finalizer but this is not always true (see below for more information).

For our example class we basically want to call the Close method.  Therefore we can do this.

public class FileBase : IDisposable
{
   ~FileBase ( )
   {
      Close();
   }

   private IntPtr m_pUnmanagedResource;

   public void Close ( )
   {
      if (m_pUnmanagedResource != IntPtr.Zero)
      {
          //Free it
          m_pUnmanagedResource = IntPtr.Zero;
      };
   }

   void IDisposable.Dispose ( )
   { Close(); }
}

The above code ensures that the file is closed whether the user does it manually or not.  However there is a problem.  GC is non-deterministic for all objects.  Therefore any references we have within our class might or might not have already been freed by the time our finalizer is called.  Therefore when our finalizer is called we can not refer to any reference fields within our class.  Therefore we need to somehow tell our Close method not to refer to these fields.  The defacto method is to define a private (or protected) method called Dispose that accepts a boolean argument indicating whether we are disposing (i.e manually) or not (being invoked through GC).  Within this helper method is where we do the actual clean up work.

public class FileBase : IDisposable
{
   ~FileBase ( )
   {
        Dispose(false);
   }

   private IntPtr m_pUnmanagedResource;

   public void Close ( )
   {
      Dispose(true);
   }

   private void Dispose ( bool disposing )
   {
      if (disposing)
      {
         //We can access reference fields in here only
      };

      //Only value fields and unmanaged fields are
      //accessible from this point on
      if (m_pUnmanagedResource != IntPtr.Zero)
      {
          //Free it
          m_pUnmanagedResource = IntPtr.Zero;
      };
   }

   void IDisposable.Dispose ( )
   { Close(); }
}

The above code works but is suboptimal in, what we hope is, the common case of a client explicitly freeing the object.  If the client calls Close then we really do not need a finalizer anymore.  Therefore we want to tell .NET not to call our finalizer if we have already called Close.  This requires a one line addition to our Close method.

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

The GC.SuppressFinalize method tells .NET not to call the finalizer for the object specified as a parameter.  Since we already cleaned up the object there is no benefit in calling it anyway.

This completes our implementation of IDisposable.

Using

C# and VB.NET both support the using statement.  This statement should be used whenever dealing with IDisposable objects.  The statement ensures that the object is explicitly disposed when it goes out of scope.  Since this is the best behavior you should use it in almost all cases.  Here is an example of using the statement.

public string ReadFile ( string fileName )
{
   using(File file = new File(fileName))
   {
      ...
   };
}

When the File object goes out of scope at the end of the using statement the IDisposable.Dispose method will be automatically called.  In our case it will internally call Close which calls Dispose(true) to clean up the unmanaged resources.

In the few cases where using can not be used then use a try-finally block instead, like so.

public string ReadFile ( string fileName )
{
   File file = null;

    try
   {
      ...
   } finally
   {
      if (file != null)
         file.Close();
   };
}

This is not as clean as using but it works.

When To Implement

The IDisposable interface should only be implemented when it is needed.  Here are the common cases where it should be implemented.

  1. When a type contains an unmanaged or shared resource it should implement the interface and a finalizer.
  2. When a type contains fields that implement IDisposable then the type should implement the interface but it SHOULD NOT implement a finalizer.
  3. When a type uses a lot of memory internally it should consider implementing IDisposable but it SHOULD NOT implement a finalizer.

Caveats

Finally here are some caveats about the interface.

  1. An object can be disposed multiple times.  Therefore your dispose method must handle this case.
  2. When the GC calls your finalizer it will be on an arbitrary thread.  Therefore do not access any thread-specific values.
  3. The GC runs all finalizers on the same thread and there is no exception handling so the dispose method should not throw exceptions nor deadlock.
  4. Some objects can support resurrection (meaning they are disposed and then recreated).  This is difficult to do properly and should be avoided.  Assume that a disposed object is permanently gone.
  5. The dispose method can be called on multiple threads so thread-safety should be taken into account.
  6. When called from the finalizer the dispose method can not reference any reference fields.