views:

1018

answers:

6

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 Shoes, 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?

+11  A: 

You can't really "prevent" IDisposable from spreading. Some classes need to be disposed, like AutoResetEvent, and the most efficient way is to do it in the Dispose() method to avoid the overhead of finalizers. But this method must be called somehow, so exactly as in your example the classes that encapsulate or contain IDisposable have to dispose these, so they have to be disposable as well, etc. The only way to avoid it is to:

  • avoid using IDisposable classes where possible, lock or wait for events in single places, keep expensive resources in single place, etc
  • create them only when you need them and dispose them just after (the using pattern)
Grzenio
+9  A: 

In terms of correctness, you can't prevent the spread of IDisposable through an object relationship if a parent object creates and essentially owns a child object which must now be disposable. FxCop is correct in this situation and the parent must be IDisposable.

What you can do is avoid adding an IDisposable to a leaf class in your object hierarchy. This is not always an easy task but it's an interesting excercise. From a logical perspective, there is no reason that a shoe lace needs to be disposoble. Intsead of adding a wait handle here, is it also possible to add an association between a ShoeLace and a WaitHandle at the point it's used. The simplest way is through an Dictionary instance.

If you can move the WaitHandle into a loose association via a map at the point the WaitHandle is actually used then you can break this chain.

JaredPar
It feels very strange to make an association there. This AutoResetEvent is private to the Shoelace implementation, so exposing it publicly would be wrong in my opinion.
GrahamS
@GrahamS, i'm not saying expose it publically. I'm saying can it be moved to the point where shoelaces are tied. Perhaps if tying shoe laces is such an intricate task their should be a shoelace tier class.
JaredPar
Could you expand your answer with some code snippets JaredPar? I may be stretching my example a little but I am imagining that Shoelace creates and starts Tyer thread which waits patiently at waitHandle.WaitOne() The Shoelace would call waitHandle.Set() when it wanted the thread to start tying.
GrahamS
+1 on this. I think it would be strange that just Shoelace would be called concurrently but not the other ones. Think of what should be the aggregate root. In this case it should be Bus, IMHO, although i'm unfamiliar with the domain. So, in that case, the bus should contain the waithandle and all operations against the bus and all it 's children will be synchronized.
johannesg
+1  A: 

This feels a lot like a higher-level design issue, as is often the case when a "quick fix" devolves into a quagmire. For more discussion of ways out, you might find this thread helpful.

Adam
The AutoResetEvent is just an example really. I'm not looking for a solution to the threading aspect, because the same problem occurs if Shoelace create any member that is IDisposable.
GrahamS
+2  A: 

Interestingly if Driver is defined as above:

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

Then when Shoe is made IDisposable, FxCop (v1.36) does not complain that Driver should also be IDisposable.

However if it is defined like this:

class Driver
{
    Shoe leftShoe = new Shoe();
    Shoe rightShoe = new Shoe();
}

then it will complain.

I suspect that this is just a limitation of FxCop, rather than a solution, because in the first version the Shoe instances are still being created by the Driver and still need to be disposed somehow.

GrahamS
+2  A: 

I don't think there is a technical way of preventing IDisposable from spreading if you keep your design so tightly coupled. One should then wonder if the design is right.

In your example, I think it makes sense to have the shoe own the shoelace, and maybe, the driver should own his/her shoes. However, the bus should not own the driver. Typically, bus drivers do not follow buses to the scrapyard :) In the case of drivers and shoes, drivers seldom make their own shoes, meaning they don't really "own" them.

An alternative design could be:

class Bus
{
   IDriver busDriver = null;
   public void SetDriver(IDriver d) { busDriver = d; }
}

class Driver : IDriver
{
   IShoePair shoes = null;
   public void PutShoesOn(IShoePair p) { shoes = p; }
}

class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
   Shoelace lace = new Shoelace();
}

class Shoelace : IDisposable
{
   ...
}

The new design is unfortunately more complicated, as it requires extra classes to instantiate and dispose of concrete instances of shoes and drivers, but this complication is inherent to the problem being solved. The good thing is that buses need no longer be disposable simply for the purpose of disposing of shoelaces.

Joh
This doesn't really solve the original problem though. It might keep FxCop quiet but something still should dispose the Shoelace.
AndrewS
I think that all you've done is move the problem elsewhere. ShoePairWithDisposableLaces now owns Shoelace(), so it must also be made IDisposable - so who disposes of the shoes? Are you leaving this to some IoC container to handle?
GrahamS
@AndrewS: Indeed, something must still dispose of drivers, shoes and laces, but the disposal should not be initiated by buses.@GrahamS: You are right, I am moving the problem elsewhere, because I believe it belongs elsewhere. Typically, there would be a class instantiating shoes, which would also be responsible for disposing of shoes. I have edited the code to make ShoePairWithDisposableLaces disposable too.
Joh
@Joh: Thanks. As you say, the issue with moving it elsewhere is that you create a whole lot more complexity. If ShoePairWithDisposableLaces is created by some other class, then wouldn't that class then have to be notified when the Driver has finished with his Shoes, so that it can properly Dispose() of them?
GrahamS
@Graham: yes, a notification mechanism of some sort would be needed. A disposal policy based on reference counts could be used, for instance. There's some added complexity, but as you probably know, "There's no such thing as a free shoe" :)
Joh
+1  A: 

To prevent IDisposable from spreading, you should try to encapsulate the use of a disposable object inside of a single method. Try to design Shoelace differently:

class Shoelace { 
  bool tied = false; 

  public void Tie() {

    using (var waitHandle = new AutoResetEvent(false)) {

      // you can even pass the disposable to other methods
      OtherMethod(waitHandle);

      // or hold it in a field (but FxCop will complain that your class is not disposable),
      // as long as you take control of its lifecycle
      _waitHandle = waitHandle;
      OtherMethodThatUsesTheWaitHandleFromTheField();

    } 

  }
} 

The scope of the wait handle is limited to the Tiemethod, and the class doesn't need to have a disposable field, and so won't need to be disposable itself.

Since the wait handle is an implementation detail inside of the Shoelace, it shouldn't change in any way its public interface, like adding a new interface in its declaration. What will happen then when you don't need a disposable field anymore, will you remove the IDisposable declaration? If you think about the Shoelace abstraction, you quickly realize that it shouldn't be polluted by infrastructure dependencies, like IDisposable. IDisposable should be reserved for classes whose abstraction encapsulate a resource that calls for deterministic clean up; i.e., for classes where disposability is part of the abstraction.

Jordão
@Jordão: I completely agree that the implementation detail of Shoelace should not pollute the public interface, but my point is that it can be difficult to avoid. What you suggest here wouldn't always be possible: the purpose of the AutoResetEvent() is to communicate between threads, so its scope would usually extend beyond a single method.
GrahamS
@GrahamS: that's why I said to try to design it that way. You can also pass the disposable to other methods. It only breaks down when external calls to the class control the lifecycle of the disposable. I'll update the answer accordingly.
Jordão
@Jordão: sorry, I know you can pass the disposable around, but I still don't see that working. In my example the `AutoResetEvent` is used to communicate between different threads that operate within the same class, so it has to be a member variable. You can't limit its scope to a method. (e.g. imagine that one thread just sits waiting for some work by blocking on `waitHandle.WaitOne()`. The main thread then calls the `shoelace.Tie()` method, which just does a `waitHandle.Set()` and returns immediately).
GrahamS