16

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.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
  • 2
    I think you need to ask why you are trying to do this. The Dispose() pattern is not intended to implement control logic by raising exceptions. – Mitch Wheat Nov 29 '09 at 13:10
  • 1
    It's always a fair point to question the whole idea. I appreciate that this is not how "using" is meant to be used. I appreciate that it can lead to bad code. I am still interested in an answer :) – Roman Starkov Nov 29 '09 at 13:12
  • "using" statement gets transformed into 'try/finally' block & object is disposed. – SoftwareGeek Jul 29 '10 at 02:15

5 Answers5

13

https://www.codewrecks.com/post/old/2008/07/detecting-if-finally-block-is-executing-for-an-manhandled-exception/ describes a "hack" to detect if your code is executed in exception handling mode or not. It uses Marshal.GetExceptionPointers to see if an exception is "active".

But keep in mind:

Remarks

GetExceptionPointers is exposed for compiler support of structured exception handling (SEH) only. NoteNote:

This method uses SecurityAction.LinkDemand to prevent it from being called from untrusted code; only the immediate caller is required to have SecurityPermissionAttribute.UnmanagedCode permission. If your code can be called from partially trusted code, do not pass user input to Marshal class methods without validation. For important limitations on using the LinkDemand member, see Demand vs. LinkDemand.

Teejay
  • 7,210
  • 10
  • 45
  • 76
VolkerK
  • 95,432
  • 20
  • 163
  • 226
  • 5
    This is probably the only actual answer to your question, but it seems a seriously bad idea to pursue. – Paul Turner Nov 29 '09 at 13:26
  • 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. – Roman Starkov Nov 29 '09 at 13:28
  • 3
    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 Nov 29 '09 at 14:01
  • 2
    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 Nov 29 '09 at 14:42
  • 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. – Roman Starkov Mar 09 '10 at 23:07
8

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.

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
  • 3
    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 – Roman Starkov May 06 '10 at 18:34
4

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
  • 122,218
  • 32
  • 205
  • 338
2

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
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 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. – Roman Starkov Nov 29 '09 at 13:10
  • 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 Nov 29 '09 at 13:11
  • 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. – Roman Starkov Nov 29 '09 at 13:22
  • 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 Nov 29 '09 at 13:24
2

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
  • 17,882
  • 7
  • 41
  • 53