So first off this is not new, it’s been around for a long, long while. One issue I have with IDisposable is that for whatever reason .Net did not include a base class for handling the details… I admit you may not always be able to leverage it since you may have a base class, yet for me that seems a rare case. So taking time to fix a few issues in this recommendation, I finally rolled this base class: CSharpTest.Net.Bases.Disposable.
I thought I’d explain some deviations from some normal practices in this implementation:
1. – Finalizers should not throw… ever. It does bad things to the current GC cleanup.
~Disposable()
{ try { OnDispose(false); } catch { } }
2. – If the Dispose implementation throws in a call to Dispose there is no need to try again on Finalize.
public void Dispose()
{
try { OnDispose(true); }
finally { GC.SuppressFinalize(this); }
}
3. – How does a derived class know if it’s been disposed and should throw an ObjectDisposedException? It should be easy, call this.Assert():
protected virtual void Assert()
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
}
4. – What about knowing when an object is disposed? This is often a necessity, or you may simply what to chain a few more objects to be disposed along with this one. IMHO, The Disposed event might even have been part of IDisposable (or maybe a derived interface). Having this event greatly reduces the need for things like DisposingList<T> and the DisposingStream.
private event EventHandler DisposedEvent;
public event EventHandler Disposed
{
add { Assert(); DisposedEvent += value; }
remove { DisposedEvent -= value; }
}
5. – Lastly we just need to ensure the Dispose happens correctly and fire the event. You might notice I did not ensure the event would fire. The reason is simple, say Dispose(bool) throws and then a finally or catch block fires the event… and that event throws a new exception. There is not a really nice way to handle something like this, perhaps the dispose of this caused the event’s handler to fail? I felt the best answer was to propagate the first exception encountered and by doing that I prevent masking errors. Regardless of the outcome we will not try again, we are disposed and the event is cleared.
private bool _isDisposed = false;
private void OnDispose(bool disposing)
{
try
{
if (!_isDisposed)
{
Dispose(disposing);
if (DisposedEvent != null)
DisposedEvent(this, EventArgs.Empty);
}
}
finally
{
_isDisposed = true;
DisposedEvent = null;
}
}