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.
- 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 your dispose method must
handle this case.
- When the GC calls your finalizer it will be on an arbitrary thread. Therefore
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.
|