开发者

Determining normal/exceptional exit from a using block in .net

Is it possible to tell the difference between the normal exit of a using block and an exit that is due to an exception being thrown? Using try/catch/finally I can write:

Foo f = new Foo();
try 
{
    f.stuff();
} 
catch() 
{
    f.ThereWasAnError();
} 
finally 
{
    f.Dispose();
}

but it would be nice if the users of my class didn't need to care about this, and could write

using (var f = new Foo()) { ... }

I can't see a way to t开发者_开发问答ell in the Dispose method why I'm being called. Is it possible?


It would be helpful if you'd explain what you're trying to do. I suspect you are trying to do something like this:

using(t = new Transaction())
{
    DoStuff(t);
}

where the implementation of Transaction.Dispose() you want to write looks like this:

void Dispose()
{
    if (I'm being disposed because of an exception)
        Roll back the transaction
    else
        commit the transaction
}

That's an abuse of the disposable pattern. The purpose of the disposable pattern is to politely release unmanaged resources as soon as possible regardless of why the object is being disposed. It's not to implement transaction semantics. If you want to have transaction semantics then you're going to have to write the code yourself; that pattern isn't built into the C# language. The way to write that is:

Transaction t = new Transaction();
try
{
    DoStuff(t);

}
catch
{
    t.Rollback();
    throw;
}
t.Commit();

(Keeping in mind of course that there could be a thread-abort exception thrown before the commit but after the DoStuff; if that bothers you, then you're going to need to write constrained execution region code.)

If you want to make it easier on your users then encapsulate that in a higher-order helper method:

static void WithTransaction(Action<Transaction> action)
{
    Transaction t = new Transaction();
    try
    {
        action(t);
    }
    catch
    {
        t.Rollback();
        throw;
    }
    t.Commit();
}
....
WithTransaction(t=>DoStuff(t));


If Dispose() has to know if there was an error or it has to be called only if everything went fine, then it is a bad Dispose implementation.

Dispose is not about error handling, it is about ensuring that in any condition your unmanaged resource gets released.

You would do your error handling as usual but put using block inside the try block:

try
{
   using(...)
   {
      ...
   }
}
catch(Exception e)
{
   ...
}


Is it possible to tell the difference between the normal exit of a using() block and [...] an exception being thrown?

That's not the purpose of a using block, so: No.

catch() 
{
  f.ThereWasAnError();
}

If you really want some form of error handling inside your objects, than built it in. It shouldn't need outside help. That would probably require a try/catch in every public method, but that;s the price.

But I would be very suspicious of such a design.


I do not recommend the following. As Eric Lippert writes, this is an abuse of the disposable-pattern.

That being said, the following should work:

public void Dispose(){
    if(Marshal.GetExceptionCode()==0){
        // disposing normally
        Commit();
    }else{
        // disposing because of exception.
        Rollback();
    }
}


Not that I know of, but on first glance, I would skip the catch block and let the error bubble up. YMMV.


There isn't any way to have CLR tell your Dispose method that an exception is about to be thrown.

But the strange thing is that you want to "notify" your own Foo class about an exception which occurred in its own Stuff method. Doing that is very unusual; have you ever seen a class which you need to notify that it just threw an exception?

You should use the IDisposable pattern to do some unmanaged cleanup after your class is not needed anymore. From your question, I suspect that your class performs lots of actual functionality inside the Dispose method.


use try{}catch{} inside using()block.


Well. If, when you are disposing, you only care whether an exception was thrown by the instance being disposed, you could cache the exception:

class Foo
{
    Exception _thrownException;
    void Stuff()
    {
        try
        {
            //...
        }
        catch (Exception e)
        {
            _thrownException = e;
            throw;
        }
  //... I suppose you get the idea from here.

However, this would not let you know if something else in the using block threw the exception, for example:

using (var f = new Foo())
{
    int i = f.IntegerMethod() / 0;
    ...
}


I have done

using(Foo f = new Foo())
{     
  f.stuff(); 
  f.IsOk();
}  

Then the dispose method knows if it was called due to an exception or just hidding the end of the "try" or "using" block.

Sometimes "IsOk()" is called "Commit()"...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜