The First Rule of Implementing IDisposable and Finalizers
• CommentsDon’t do it (unless you need to).
IDisposable
is not a destructor. Remember that .NET has a garbage collector that works just fine without requiring you to set member variables to null.
There are only two situations when IDisposable
does need to be implemented; apply these tests to a class to determine if IDisposable
is needed:
- The class owns unmanaged resources.
- The class owns managed (
IDisposable
) resources.
Note that only classes that own resources should free them. In particular, a class may have a reference to a shared resource; in this case, it should not free the resource because other classes may still be using it.
Here’s a code example similar to what many beginner C# programmers write:
// This is an example of an incorrect IDisposable implementation.
public sealed class ErrorList : IDisposable
{
private string category;
private List<string> errors;
public ErrorList(string category)
{
this.category = category;
this.errors = new List<string>();
}
// (other methods go here to add/display error messages)
// Completely unnecessary...
public void Dispose()
{
if (this.errors != null)
{
this.errors.Clear();
this.errors = null;
}
}
}
Some programmers (especially with C++ backgrounds) even go a step further and add a finalizer:
// This is an example of an incorrect and buggy IDisposable implementation.
public sealed class ErrorList : IDisposable
{
private string category;
private List<string> errors;
public ErrorList(string category)
{
this.category = category;
this.errors = new List<string>();
}
// (other methods go here to add/display error messages)
// Completely unnecessary...
public void Dispose()
{
if (this.errors != null)
{
this.errors.Clear();
this.errors = null;
}
}
~ErrorList()
{
// Very bad!
// This can cause an exception in the finalizer thread, crashing the application!
this.Dispose();
}
}
The correct implementation of IDisposable
for this type is here:
// This is an example of a correct IDisposable implementation.
public sealed class ErrorList
{
private string category;
private List<string> errors;
public ErrorList(string category)
{
this.category = category;
this.errors = new List<string>();
}
}
That’s right, folks. The correct IDisposable
implementation for this class is to not implement IDisposable
! When an ErrorList
instance becomes unreachable, the garbage collector will automatically reclaim all of its memory and resources.
Remember the two tests to determine if IDisposable
is needed (owning unmanaged resources and owning managed resources). A simple checklist can be done as follows:
- Does the
ErrorList
class own unmanaged resources? No, it does not. -
Does the
ErrorList
class own managed resources? Remember, “managed resources” are any classes implementingIDisposable
. So, check each owned member type: - Does
string
implementIDisposable
? No, it does not. - Does
List<string>
implementIDisposable
? No, it does not. -
Since none of the owned members implement
IDisposable
, theErrorList
class does not own any managed resources. - Since there are no unmanaged resources and no managed resources owned by
ErrorList
, it does not need to implementIDisposable
.