I am not a fan of boilerplate code: copy-paste reuse is potentially error-prone. Even if you use code snippets or smart templates, there is no guarantee the other developer did, which means there's no guarantee they did it right. And, if you have to see the code, you have to understand it and/or maintain it.
What I want to know from the community is: is my implementation of IDispose for a class hierarchy a legitimate alternative to the "traditional" dispose pattern? By legitimate, I mean correct, reasonably well performing, robust, and maintainable.
I am ok with this alternative being plain wrong, but if it is, I'd like to know why.
This implementation assumes you have full control over the class hierarchy; if you don't you'll probably have to resort back to boilerplate code. The calls to Add*() would typically be made in the constructor.
public abstract class DisposableObject : IDisposable
{
protected DisposableObject()
{}
protected DisposableObject(Action managedDisposer)
{
AddDisposers(managedDisposer, null);
}
protected DisposableObject(Action managedDisposer, Action unmanagedDisposer)
{
AddDisposers(managedDisposer, unmanagedDisposer);
}
public bool IsDisposed
{
get { return disposeIndex == -1; }
}
public void CheckDisposed()
{
if (IsDisposed)
throw new ObjectDisposedException("This instance is disposed.");
}
protected void AddDisposers(Action managedDisposer, Action unmanagedDisposer)
{
managedDisposers.Add(managedDisposer);
unmanagedDisposers.Add(unmanagedDisposer);
disposeIndex++;
}
protected void AddManagedDisposer(Action managedDisposer)
{
AddDisposers(managedDisposer, null);
}
protected void AddUnmanagedDisposer(Action unmanagedDisposer)
{
AddDisposers(null, unmanagedDisposer);
}
public void Dispose()
{
if (disposeIndex != -1)
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
~DisposableObject()
{
if (disposeIndex != -1)
Dispose(false);
}
private void Dispose(bool disposing)
{
for (; disposeIndex != -1; --disposeIndex)
{
if (disposing)
if (managedDisposers[disposeIndex] != null)
managedDisposers[disposeIndex]();
if (unmanagedDisposers[disposeIndex] != null)
unmanagedDisposers[disposeIndex]();
}
}
private readonly IList<Action> managedDisposers = new List<Action>();
private readonly IList<Action> unmanagedDisposers = new List<Action>();
private int disposeIndex = -1;
}
This is a "complete" implementation in the sense I provide support for finalization (knowing most implementations don't need a finalizer), checking whether an object is disposed, etc. A real implementation may remove the finalizer, for example, or create a subclass of DisposableObject that includes the finalizer. Basically, I threw in everything I could think of just for this question.
There are probably some edge cases and esoteric situations I've missed, so I invite anyone to poke holes in this approach or to shore it up with corrections.
Other alternatives might be to use a single Queue<Disposer> disposers in DisposableObject instead of two lists; in this case, as disposers are called, they are removed from the list. There are other slight variations I can think of, but they have the same general result: no boilerplate code.