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; } }