I am often asked how to correctly implement the IDisposable
pattern. There are many posts you'll see from a Google search that try to answer this question. IMHO, the pattern that you find here is the safest and most correct implementation of the pattern according to .NET Best Practices and my experience. I am confident saying this because of my extensive experience with memory leak debugging, internals of the CLR, code reviews, and implementation of best practices at very large, well-known clients who rely on solid applications. You will find that my provided implementation of this pattern is slightly different than that which is posted on MSDN. IMHO, it isn't sufficient. You have to agree that a large number of samples and examples posted on MSDN are "hacky" and don't do things the way that they should be done in the "real world".
A couple of notes on implementing the IDisposable
pattern:
- Simply adding a
Dispose()
method to a class doesn't implement the pattern (just because it compiles doesn't mean it's correct). I've seen this mistake made many, many times - it's just not a good idea. - Adding the code below isn't enough - you must also explicitly declare the class inheriting from
IDisposable -> MyDisposableClass : IDisposable
. This is very important since objects creating/managing instances of your class might decide to try to cast it to IDisposable
and call Dispose
or use a 'using()
' statement. If your class isn't explicitly declared to implement IDisposable
, the cast will fail and Dispose
will never be called. - Just because you implement
IDisposable
correctly doesn't mean it will get called "magically". What I mean is that the GC never calls Dispose
for you - you have to explicitly call it on your class from the owning object. There is a common misconception that this can somehow magically happen by the GC and that it's not important that you explicitly call it "because the GC cleans up for you" (WRONG!) - Implementing the
IDisposable
pattern correctly is tricky and can easily lead to problems if not done properly. This is why I've provided the code below. - I use a code snippet with the exact code below to properly and consistently implement the pattern correctly.
- There is great debate on whether or not you should null out rooted references. It certainly never hurts to do so. Nulling these out does do something before GC runs - it removes the rooted reference to that object. The GC later scans its collection of rooted references and collects those that do not have a rooted reference.
Think of this example when it is good to do so: you have an instance of type "ClassA
" - let's call it 'X'. X contains an object of type "ClassB
" - let's call this 'Y'. Y implements IDisposable
, thus, X should do the same to dispose of Y. Let's assume that X is in Generation 2 or the LOH and Y is in Generation 0 or 1. When Dispose()
is called on X AND that implementation nulls out the reference to Y, the rooted reference to Y is immediately removed. If a GC happens for Gen 0 or Gen 1, the memory/resources for Y is cleaned up but the memory/resources for X is not since X lives in Gen 2 or the LOH.
Here is the code for properly implementing the IDisposable
pattern in a base class:
#region IDisposable base-class implementation
private bool IsDisposed { get; set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool isDisposing)
{
try
{
if (!this.IsDisposed)
{
if (isDisposing)
{
if (someDisposableObjectWithAnEventHandler != null)
{
someDisposableObjectWithAnEventHandler.SomeEvent -= someDelegate;
someDisposableObjectWithAnEventHandler.Dispose();
someDisposableObjectWithAnEventHandler = null;
}
}
if (someComObject != null && Marshal.IsComObject(someComObject))
{
Marshal.FinalReleaseComObject(someComObject);
someComObject = null;
}
}
}
finally
{
this.IsDisposed = true;
}
}
Here is the code for properly implementing the IDisposable
pattern in a derived class:
#region IDisposable derived-class implementation
private bool IsDisposed { get; set; }
protected override void Dispose(bool isDisposing)
{
try
{
if (!this.IsDisposed)
{
if (isDisposing)
{
if (someDisposableObjectWithAnEventHandler != null)
{
someDisposableObjectWithAnEventHandler.SomeEvent -= someDelegate;
someDisposableObjectWithAnEventHandler.Dispose();
someDisposableObjectWithAnEventHandler = null;
}
}
if (someComObject != null && Marshal.IsComObject(someComObject))
{
Marshal.FinalReleaseComObject(someComObject);
someComObject = null;
}
}
}
finally
{
this.IsDisposed = true;
base.Dispose(isDisposing);
}
}
#endregion IDisposable derived-class implementation