September 20, 2009

Delegate.Invoke() vs using(...) {...} as prologue-epilogue implementation approach

We frequently use using(...) { ... } construction to wrap some code by a common prologue and epilogue actions. So why we prefer using? Let's list pros and cons of this on some examples.

Pros:

1. Performance. It appears that using requires additional allocation, but delegate invocation does not imply this. This is actually not true:
  • You can return the same (e.g. static) object from using construction. So prologue is actually a method call returning this object, and its disposer (IDisposable.Dispose) is epilogue. No allocations at all. Disposer will be called multiple times for the same object, but this is fully in accordance with IDisposable usage conventions.
  • You can return even null, if you don't need epilogue. Not obvious path (and we're going to get rid of all such cases in our code; we'll return static object instead), but it works.
Finally, delegates frequently involve closures. E.g. this code:
public double SillyPower(double x, int power)
{
  double power = 1;
  for (int i = 0; i < power; i++) {
    () => {
      power = power * x;
    }.Invoke();
  }
}
will be translated by C# compiler as nearly this one:
public double SillyPower(double x, int power)
{
  var _closure = new {
    power = 1d, 
    x = x, 
    handler = { power = power * x; } // Pseudo-code, 
    // actually you can't declare methods in anonymous types 
    // by this way. There is simply no way to do this at all ;)
    // But C# compiler can.
  };
  var _delegate = (Action()) _closure.handler;
  for (var i = 0; i < _closure.power; i++)
    _delegate.Invoke();
}
As you see, this code makes 2 allocations per each invocation of such method: the first one is closure instance allocation, and the second one is delegate allocation. Moreover, access to each variable placed into the closure from the SillyPower method scope requires additional reference traversal, because such variables are located in heap rather than on stack now.

If you're interested in details, read e.g. this article about closures and enumerators.

2. Better readability. Yes, I think version with using statement is more readable than in-place delegate creation.

Cons:

The only one I see is possibility to get original exception hidden. Let's have a look at this code:
public void Test()
{
  try {
    using (var s = SomeScope.Open()) {
      throw new FirstException();
    } // Imagine that SecondException is thrown in s.Dispose() here
  }
  catch (Exception e) {
    // e is SecondException here;
    // FirstException is completely lost.
  }
}
So here is the problem: if IDisposable.Dispose throws an exception, it has no opportunity to get information about any exception that is already falling through the stack. That's why it's recommended to avoid throwing any exceptions from Dispose method (moreover, you should do all you can to prevent the same from any code invoked by it).

But what if we need to throw an exception from Dispose? Is there a solution? Actually, yes. The most well-known one is to use Complete() - like method:
public void Test()
{
  using (var s = SomeScope.Open()) {
    if (new Random().Next(2)!=0)
      throw new FirstException();
    s.Complete(); // If this method isn't invoked, likely,
                  // above code has thrown an exception.
                  // So s.Dispose() should not throw an
                  // exception in any case.
    // Here we know that no exception was thrown in this block;
    // so it's safe to throw an exception from s.Dispose().
  }
}
Btw, inconsistency regions in DataObjects.Net v4.0.5 were vulnerable to this issue, but we've fixed it few weeks ago by exactly this way.

Note that there is no such problem with delegates: you can wrap delegate invocation into try-catch-finally block with ease.

So may be C# developers should think about providing ISafeDisposabe:
public interface ISafeDisposable : IDisposable
{
  void Dispose(Exception exception);
}
Really simple, yes? ;) Old IDisposable.Dispose must simply forward its call to ISafeDisposabe.Dispose(null), if ISafeDisposabe is implemented. Moreover, it's easy to make C# compiler implementing such a legacy Dispose automatically (like auto property). If ISafeDisposable is implemented, using statement must rely on it; otherwise it must rely on legacy IDisposable. So finally we have full backward compatibility.

Ok, that's just one of my dreams ;) Have a nice weekend!