views:

67

answers:

4

Is there any nice pattern in .Net for ensuring that iDisposable fields owned by an object will get disposed if an exception is thrown during construction, possibly during a field initializer? The only way to surround field initializers in a Try/Catch block is if the block is outside the call to the constructor, which will make it rather difficult for cleanup code to properly dispose of anything.

The only approach I can figure would be to the object inherit from a base class whose constructor takes something like an array of iDisposable, and sets the first item in that array to point to itself. All constructors the descendant classes should be Private or Orotected, and include that parameter. Instantiation should be via factory methods, which will declare an array of one iDisposable and pass it to the appropriate constructor. If the constructor fails, the factory method will have a reference to the partially-constructed object, which it can then dispose (the dispose method must, of course, be prepared to accept the possibility that the object may not be fully constructed).

The approach could be extended by having the object keep a list of iDisposable objects it creates, to allow the objects to be cleaned up without having to explicitly dispose each one; such a list would be useful in conjunction with the factory-method-calls-dispose approach, but is largely orthogonal to it.

Any thoughts?

+1  A: 

Holding on to a partially constructed object sounds dangerous to me, if it would even work. I wouldn't use initializers or a ctor to handle this.

How about if instead, you use an object factory (not quite the same as a class factory) to create your object.

The constructor of your object would not be responsible for creating the IDisposable objects that it owns. Instead, the factory would create each IDisposable and it would call the constructor on your owner object. The factory would then set the appropriate members in the owner object to the disposable objects that were created.

pseudocode:


public superobject CreateSuperObject()
{
   IDisposable[] members = new IDisposable[n]
   try
     SuperObject o = new SuperObject()
     // init the iDisposable members, add each to the array, (you will probably also nee
     o.DisposableMember1 = new somethingdisposeable();
     members[0] = o.DisposeableMember1

     return o;
   catch
      // loop through the members array, disposing where not null
      // throw a new exception??
}

JMarsch
A: 

In C# you would use 'using':

        using(DisposableObject obj = new DisposableObject()) {
        }

VB also has a Using...End Using construct. When using these the Dispose method is guaranteed to be called, even in the event of an exception. You can release any resources created by the initializers (or the constructor) in the Dispose method.

Steve Ellinger
-1 This doesn't work if the constructor throws an exception. The new object is never returned, so there is nothing to call Dispose on.
chilltemp
Hmmmm...busted.
Steve Ellinger
+1  A: 

You should catch any exceptions in the constructor, then dispose of your child objects, then rethrow the original exception (or a new exception that provides additional information).

public class SomethingDisposable : IDisposable
{
  System.Diagnostics.Process disposableProcess;
  public SomethingDisposable()
  {
    try
    {
      disposableProcess = new System.Diagnostics.Process();
      // Will throw an exception because I didn't tell it what to start
      disposableProcess.Start();
    }
    catch
    {
      this.Dispose();
      throw;
    }
  }

  public void Dispose()
  {
    if (disposableProcess != null)
    {
      disposableProcess.Dispose();
      disposableProcess = null;
    }
  }
}
chilltemp
Is there any way to catch exceptions in initializers (e.g. "Font myFont = New Font("Arial", ...whatever...);")? In many cases, it's far more convenient to create objects when they're defined than to define them in one place and then create them somewhere else.
supercat
@supercat: Not that I'm aware of. I agree with the simplicity that you seek, but in this case being robust is more important.
chilltemp
A: 
supercat