views:

372

answers:

6

We're investigating a coding pattern in C# in which we'd like to use a "using" clause with a special class, whose Dispose() method does different things depending on whether the "using" body was exited normally or with an exception.

To the best of my understanding, the CLR keeps track of the current exception being handled until it's been consumed by a "catch" handler. However it's not entirely clear whether this information is exposed in any way for the code to access. Do you know whether it is, and if so, how to access it?

For example:

using (var x = new MyObject())
{
    x.DoSomething();
    x.DoMoreThings();
}

class MyObject : IDisposable
{
    public void Dispose()
    {
        if (ExceptionIsBeingHandled)
            Rollback();
        else
            Commit();
    }
}

This looks almost like System.Transactions.TransactionScope, except that success/failure is not determined by a call to x.Complete(), but rather based on whether the using body was exited normally.

+2  A: 

A using statement is just syntactic sugar for a try finally block. You can get what you want by writing the try finally out in full and then adding a catch statement to handle your special case:

try
{
    IDisposable x = new MyThing();
}
catch (Exception exception) // Use a more specific exception if possible.
{
    x.ErrorOccurred = true; // You could even pass a reference to the exception if you wish.
    throw;
}
finally
{
    x.Dispose();
}

Inside MyThing you can do this if you want, for example:

class MyThing : IDisposable
{
    public bool ErrorOccurred() { get; set; }

    public void Dispose()
    {
        if (ErrorOccurred) {
            RollBack();
        } else {
            Commit();
        }
    }
}

Note: I also have to wonder why you want to do this. It has some code smell. The Dispose method is intended to clean up unmanaged resources, not to handle exceptions. You would probably be better off writing your exception handling code in the catch block, not in the dispose, and if you need to share code make some useful helper functions that you can call from both places.

Here's a better way of doing what you want:

using (IDisposable x = new MyThing())
{
    x.Foo();
    x.Bar();
    x.CommitChanges();
}

class MyThing : IDisposable
{
    public bool IsCommitted { get; private set; }

    public void CommitChanges()
    {
        // Do stuff needed to commit.
        IsCommitted = true;
    }

    public void Dispose()
    {
        if (!IsCommitted)
            RollBack();
    }
}
Mark Byers
Ah, I should have mentioned this. The point is that the code that goes into "catch" and "finally" is identical but also non-trivial. The point is to move this code elsewhere.
romkyns
That's no problem. The dispose code gets called in both cases, but in the catch code you can execute *extra* code before dispose is called. For example, you can set some boolean inside x, which can be checked when Dispose is called.
Mark Byers
Thanks for your efforts, but if we still have to place a "normal" try/catch clause then there isn't any point in this - as you rightly observe, the stuff that comes in `if (ErrorOccurred)/else` should just go inside the `catch`/`finally`. The idea is to save repetitive try/catch/finally with a `using`, because it would cut down on boilerplate code all over the place.
romkyns
I've updated my comment, see the end of it (it's very long now). There is no try catch block any more, only an extra commit statement.
Mark Byers
+3  A: 

This information isn't available to you.

I would use a pattern similar to that used by the DbTransaction class: that is, your IDisposable class should implement a method analagous to DbTransaction.Commit(). Your Dispose method can then perform different logic depending on whether or not Commit was called (in the case of DbTransaction, the transaction will be rolled back if it wasn't explicity committed).

Users of your class would then use the following pattern, similar to a typical DbTransaction:

using(MyDisposableClass instance = ...)
{
    ... do whatever ...

    instance.Commit();
} // Dispose logic depends on whether or not Commit was called.

EDIT I see you've edited your question to show you're aware of this pattern (your example uses TransactionScope). Nevertheless I think it's the only realistic solution.

Joe
+6  A: 
VolkerK
This is probably the only actual answer to your question, but it seems a seriously bad idea to pursue.
Programming Hero
Thank you, spot on. @Programming Hero: possibly. Can't deny that it seems that way. We'll give it a go in a small test project and see if there are major issues with this approach.
romkyns
If you do go down this path, take a look at the following blog first: http://geekswithblogs.net/akraus1/archive/2008/04/08/121121.aspx
Joe
romkyns: with this kind of hack, you won't see major issues in a small test project. It's when your code is deployed to machines with 1000s of unique and different configuration quirks that you will see issues.
Anton Tykhyy
Just a note to anyone who is thinking of using this hack: it remains untested "in the wild" since we didn't end up using it; see my post below for an example of what we did instead.
romkyns
+1  A: 

This doesn't seem to be that bad an idea; it just doesn't seem to be ideal in C#/.NET.

In C++ there is a function that enables code to detect if it's being called due to an exception. This is of most importance in RAII destructors; it is a trivial matter for the destructor to choose to commit or abort depending on whether control flow is normal or exceptional. I think this is a fairly natural approach to take, but the lack of built-in support (and the morally dubious nature of the workaround; it feels rather implementation-dependent to me) probably means that a more conventional approach should be taken.

DrPizza
A: 

Has anybody figured out how System.Transactions.TransactionScope manages to accomplish this objective?

I wholly endorse that mucking about in the GetExceptionPointers method has code smell, but the big question is: If Microsoft managed to do this with the Transactions name space, is it really such a bad idea?

I'm specifically interrested because I've written an inversion of control framework and it would be ideal for the "Service" (read: IDisposable factory & manager) to be able to inform an object that something has gone wrong.

Regardless of whether it's a good or "bad" idea, knowing how Microsoft accomplished this has merit. Anyone got any ideas?

Mark
I believe that MS is _not_ doing this in `TransactionScope`. What they do instead is require you to .Commit() on exit from the `using`, and if you didn't do that treat it as indication to .Rollback().
romkyns
I'm fairly certain that the whole point of the ambient transactions are that they automatically rollback if any exception occurs within the block. I remember that in .NET 2.0, it was a problem for me because even if I handled the exception inside the block, the transaction still rolled back. Is this no longer the case?
Mark
+3  A: 

Not an answer to the question, but just a note that I never ended up using the "accepted" hack in real code, so it's still largely untested "in the wild". Instead we went for something like this:

DoThings(x =>
{
    x.DoSomething();
    x.DoMoreThings();
});

where

public void DoThings(Action<MyObject> action)
{
    bool success = false;
    try
    {
        action(new MyObject());
        Commit();
        success = true;
    }
    finally
    {
        if (!success)
            Rollback();
    }
}

The key point is that it's as compact as the "using" example in the question, and doesn't use any hacks.

Among the drawbacks is a performance penalty (completely negligible in our case), and F10 stepping into DoThings when I actually want it to just step straight to x.DoSomething(). Both very minor.

romkyns
This is much less attractive in VS2010 due to IntelliSense bugs to do with anonymous methods... https://connect.microsoft.com/VisualStudio/feedback/details/557224/intellisense-completely-fails-for-collection-inside-anonymous-method?wa=wsignin1.0
romkyns