How to determine whether a .NET exception is being handled?
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 i开发者_开发技巧s 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.
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:
RemarksGetExceptionPointers 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.
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.
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.
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();
}
}
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.
精彩评论