[ACCEPTED]-Intercepting an exception inside IDisposable.Dispose-idisposable
You can extend IDisposable
with method Complete
and use pattern 6 like that:
using (MyWrapper wrapper = new MyWrapper())
{
throw new Exception("Bad error.");
wrapper.Complete();
}
If an exception is thrown inside 5 the using
statement Complete
will not be called before 4 Dispose
.
If you want to know what exact exception 3 is thrown, then subscribe on AppDomain.CurrentDomain.FirstChanceException
event and 2 store last thrown exception in ThreadLocal<Exception>
variable.
Such 1 pattern implemented in TransactionScope
class.
No, there is no way to do this in the .Net 7 framework, you cannot figure out the current-exception-which-is-being-thrown 6 in a finally clause.
See this post on my blog, for a comparison 5 with a similar pattern in Ruby, it highlights 4 the gaps I think exist with the IDisposable 3 pattern.
Ayende has a trick that will allow 2 you to detect an exception happened, however, it will not tell you which 1 exception it was.
It is not possible to capture the Exception in the Dispose()
method.
However, it 11 is possible to check Marshal.GetExceptionCode()
in the Dispose to 10 detect if an Exception did occur, but I 9 wouldn't rely on that.
If you don't need 8 a class and want just want to capture the 7 Exception, you can create a function that 6 accepts a lambda that is executed within 5 a try/catch block, something like this:
HandleException(() => {
throw new Exception("Bad error.");
});
public static void HandleException(Action code)
{
try
{
if (code != null)
code.Invoke();
}
catch
{
Console.WriteLine("Error handling");
throw;
}
}
As 4 example, You can use a method that automatically 3 does a Commit() or Rollback() of a transaction 2 and do some logging. At this way you don't 1 always need a try/catch block.
public static int? GetFerrariId()
{
using (var connection = new SqlConnection("..."))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
return HandleTranaction(transaction, () =>
{
using (var command = connection.CreateCommand())
{
command.Transaction = transaction;
command.CommandText = "SELECT CarID FROM Cars WHERE Brand = 'Ferrari'";
return (int?)command.ExecuteScalar();
}
});
}
}
}
public static T HandleTranaction<T>(IDbTransaction transaction, Func<T> code)
{
try
{
var result = code != null ? code.Invoke() : default(T);
transaction.Commit();
return result;
}
catch
{
transaction.Rollback();
throw;
}
}
James, All wrapper
can do is log it's own exceptions. You 33 can't force the consumer of wrapper
to log their 32 own exceptions. That's not what IDisposable 31 is for. IDisposable is meant for semi-deterministic 30 release of resources for an object. Writing 29 correct IDisposable code is not trivial.
In 28 fact, the consumer of the class isn't even 27 required to call your classes dispose method, nor 26 are they required to use a using block, so 25 it all rather breaks down.
If you look at 24 it from the point of view of the wrapper 23 class, why should it care that it was present 22 inside a using block and there was an exception? What 21 knowledge does that bring? Is it a security 20 risk to have 3rd party code privy to exception 19 details and stack trace? What can wrapper
do if 18 there is a divide-by-zero in a calculation?
The 17 only way to log exceptions, irrespective 16 of IDisposable, is try-catch and then to 15 re-throw in the catch.
try
{
// code that may cause exceptions.
}
catch( Exception ex )
{
LogExceptionSomewhere(ex);
throw;
}
finally
{
// CLR always tries to execute finally blocks
}
You mention you're 14 creating an external API. You would have 13 to wrap every call at your API's public 12 boundary with try-catch in order to log 11 that the exception came from your code.
If 10 you're writing a public API then you really 9 ought to read Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series) - 2nd Edition .. 1st Edition.
While I don't advocate 8 them, I have seen IDisposable used for other 7 interesting patterns:
- Auto-rollback transaction semantics. The transaction class would rollback the transaction on Dispose if not already committed.
- Timed code blocks for logging. During object creation a timestamp was recorded, and on Dispose the TimeSpan was calculated and a log event was written.
* These patterns can 6 be achieved with another layer of indirection 5 and anonymous delegates easily and without 4 having to overload IDisposable semantics. The 3 important note is that your IDisposable 2 wrapper is useless if you or a team member 1 forget to use it properly.
You can do this buy implementing the Dispose 3 method for the "MyWrapper" class. In the 2 dispose method you can check to see if there 1 is an exception as follows
public void Dispose()
{
bool ExceptionOccurred = Marshal.GetExceptionPointers() != IntPtr.Zero
|| Marshal.GetExceptionCode() != 0;
if(ExceptionOccurred)
{
System.Diagnostics.Debug.WriteLine("We had an exception");
}
}
Instead of the syntactic sugar of the using 2 statement, why not just implement your own 1 logic for this. Something like:
try
{
MyWrapper wrapper = new MyWrapper();
}
catch (Exception e)
{
wrapper.CaughtException = true;
}
finally
{
if (wrapper != null)
{
wrapper.Dispose();
}
}
In my case, I wanted to do this to log when 12 an microservice crashes. I already have 11 in place a using
to properly clean up right before 10 an instance shut down, but if that's because 9 of an exception I want to see why, and I 8 hate no for an answer.
Instead of trying 7 to make it work in Dispose()
, perhaps make a delegate 6 for the work you need to do, and then wrap 5 your exception-capturing in there. So in 4 my MyWrapper logger, I add a method that 3 takes an Action / Func:
public void Start(Action<string, string, string> behavior)
try{
var string1 = "my queue message";
var string2 = "some string message";
var string3 = "some other string yet;"
behaviour(string1, string2, string3);
}
catch(Exception e){
Console.WriteLine(string.Format("Oops: {0}", e.Message))
}
}
To implement:
using (var wrapper = new MyWrapper())
{
wrapper.Start((string1, string2, string3) =>
{
Console.WriteLine(string1);
Console.WriteLine(string2);
Console.WriteLine(string3);
}
}
Depending 2 on what you need to do, this may be too 1 restrictive, but it worked for what I needed.
Now, in 2017, this is the generic way to 2 do it, incl handling rollback for exceptions.
public static T WithinTransaction<T>(this IDbConnection cnn, Func<IDbTransaction, T> fn)
{
cnn.Open();
using (var transaction = cnn.BeginTransaction())
{
try
{
T res = fn(transaction);
transaction.Commit();
return res;
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
cnn.Close();
}
}
}
and 1 you call it like this:
cnn.WithinTransaction(
transaction =>
{
var affected = ..sqlcalls..(cnn, ..., transaction);
return affected;
});
This will catch exceptions thrown either 16 directly or inside the dispose method:
try
{
using (MyWrapper wrapper = new MyWrapper())
{
throw new MyException("Bad error.");
}
}
catch ( MyException myex ) {
//deal with your exception
}
catch ( Exception ex ) {
//any other exception thrown by either
//MyWrapper..ctor() or MyWrapper.Dispose()
}
But 15 this is relying on them using this this 14 code - it sounds like you want MyWrapper 13 to do that instead.
The using statement is 12 just to make sure that Dispose always gets 11 called. It's really doing this:
MyWrapper wrapper;
try
{
wrapper = new MyWrapper();
}
finally {
if( wrapper != null )
wrapper.Dispose();
}
It sounds 10 like what you want is:
MyWrapper wrapper;
try
{
wrapper = new MyWrapper();
}
finally {
try{
if( wrapper != null )
wrapper.Dispose();
}
catch {
//only errors thrown by disposal
}
}
I would suggest dealing 9 with this in your implementation of Dispose 8 - you should handle any issues during Disposal 7 anyway.
If you're tying up some resource 6 where you need users of your API to free 5 it in some way consider having a Close()
method. Your 4 dispose should call it too (if it hasn't 3 been already) but users of your API could 2 also call it themselves if they needed finer 1 control.
It is not only possible to find out if an 8 exception was thrown when a disposable object 7 is disposed you can even get your hands 6 on the exception that was thrown inside 5 the finally clause with a little magic. My 4 Tracing library of the ApiChange tool employs 3 this method to trace exceptions inside a 2 using statement. More infos how this works 1 can be found here.
Yours, Alois Kraus
If you want to stay purely within .net, two 43 approaches I would suggest would be writing 42 a "try-catch-finally" wrapper, which would 41 accept delegates for the different parts, or 40 else writing a "using-style" wrapper, which 39 accept a method to be invoked, along with 38 one or more IDisposable objects which should 37 be disposed after it completes.
A "using-style" wrapper 36 could handle the disposal in a try-catch 35 block and, if any exceptions are thrown 34 in disposal, either wrap them in a CleanupFailureException 33 which would hold the disposal failures as 32 well as any exception that occurred in the 31 main delegate, or else add something to 30 the exception's "Data" property with the 29 original exception. I would favor wrapping 28 things in a CleanupFailureException, since 27 an exception that occurs during cleanup 26 will generally indicate a much bigger problem 25 than one which occurs in main-line processing; further, a 24 CleanupFailureException could be written 23 to include multiple nested exceptions (if 22 there are 'n' IDisposable objects, there 21 could be n+1 nested exceptions: one from 20 the mainline and one from each Dispose).
A 19 "try-catch-finally" wrapper written in vb.net, while 18 callable from C#, could include some features 17 that are otherwise unavailable in C#, including 16 the ability to expand it to a "try-filter-catch-fault-finally" block, where 15 the "filter" code would be executed before 14 the stack is unwound from an exception and 13 determine whether the exception should be 12 caught, the "fault" block would contain 11 code that would only run if an exception 10 occurred, but would not actually catch it, and 9 both the "fault" and "finally" blocks would 8 receive parameters indicating both what 7 exception (if any) occurred during the execution 6 of the "try", and whether the "try" completed 5 successfully (note, btw, that it would be 4 possible for the exception parameter to 3 be non-null even if the mainline completes; pure 2 C# code couldn't detect such a condition, but 1 the vb.net wrapper could).
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.