Implementing IDisposable
This article will attempt to explain what the IDisposable is, when it 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 there is little control over when it occurs.
In general this is a good thing. Does it really matter whether or not when memory gets freed? 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.
{
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. Dispose does not necessarily mean much to a caller so a separate method can be created that internally does the clean up and then explicitly implement the interface like so.
{
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 a finalizer is needed for the class. A finalizer actually delays the process of freeing the object but it allows clean up code to execute. Whenever IDisposable is implemented it is important to analyze whether a finalizer is also needed (see below for more information). Whenever a finalizer is defined IDisposable should also be implemented.
For the example class the Close method should be called to clean up the unmanaged resource.
{
~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 but there is a problem. GC is non-deterministic for all objects. Any reference fields within the class might or might not have already been freed by the time the finalizer is called. When the finalizer is called the code cannot refer to any reference fields within the class. A method is needed to tell the 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 the object is being disposed (i.e manually) or not (being invoked through GC). Within this helper method is where the actual clean up work is done.
{
~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 is hoped, the common case of a client explicitly freeing the object. If the client calls Close then the object does not need to be finalized anymore. What is needed is a way to tell .NET not to call the finalizer if the object has been disposed. This requires a one line addition to the Close method (or whatever method is the explicit cleanup method).
{
Dispose(true);
GC.SuppressFinalize(this);
}
The GC.SuppressFinalize method tells .NET not to call the finalizer for the object specified as a parameter. Since the object has already been cleaned up there is no benefit in calling it anyway. This is only needed for classes with finalizers.
This completes the 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 it should be used it in almost all cases. Here is an example of using the statement.
{
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 the example code it will internally call Close which calls Dispose(true) to clean up the unmanaged resources. Even if an exception occurs the object will be disposed.
In the few cases where using can not be used then use a try-finally block instead, like so.
{
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.
- When a type contains an unmanaged or shared resource it should implement the interface and a finalizer.
- When a type contains fields that implement IDisposable then the type should implement the interface but it SHOULD NOT implement a finalizer.
- 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.
- An object can be disposed multiple times. Therefore the dispose method must handle this case.
- When the GC calls a finalizer it will be on an arbitrary thread. Do not access any thread-specific values.
- 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.
- 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.
- The dispose method can be called on multiple threads so thread-safety should be taken into account.
- When called from the finalizer the dispose method can not reference any reference fields.