Start with these simple classes...
Let's say I have a simple set of classes like this:
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
has a Driver
, the Driver
has two Shoe
s, each Shoe
has a Shoelace
. All very silly.
Add an IDisposable object to Shoelace
Later I decide that some operation on the Shoelace
could be multi-threaded, so I add an EventWaitHandle
for the threads to communicate with. So Shoelace
now looks like this:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Implement IDisposable on Shoelace
Buit now FxCop will complain: "Implement IDisposable on 'Shoelace' because it creates members of the following IDisposable types: 'EventWaitHandle'."
Okay, I implement IDisposable
on Shoelace
and my neat little class becomes this horrible mess:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
Or (as pointed out by commenters) since Shoelace
itself has no unmanaged resources, I might use the simpler dispose implementation without needing the Dispose(bool)
and Destructor:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
Watch in horror as IDisposable spreads
Right that's that fixed. But now FxCop will complain that Shoe
creates a Shoelace
, so Shoe
must be IDisposable
too.
And Driver
creates Shoe
so Driver
must be IDisposable
.
and Bus
creates Driver
so Bus
must be IDisposable
and so on.
Suddenly my small change to Shoelace
is causing me a lot of work and my boss is wondering why I need to checkout Bus
to make a change to Shoelace
.
The Question
How do you prevent this spread of IDisposable
, but still ensure that your unmanaged objects are properly disposed?