42

In the IDisposable.Dispose method is there a way to figure out if an exception is being thrown?

using (MyWrapper wrapper = new MyWrapper())
{
    throw new Exception("Bad error.");
}

If an exception is thrown in the using statement I want to know about it when the IDisposable object is disposed.

cuongle
  • 74,024
  • 28
  • 151
  • 206
James Newton-King
  • 48,174
  • 24
  • 109
  • 130
  • Why does your wrapper implementing IDisposable need to know why it is being disposed? – Robert Paulson Oct 21 '08 at 01:28
  • 4
    I want to write a logging message and it would be nice to know whether the code inside the statement was run successfully or whether it broke out halfway through because of an exception. – James Newton-King Oct 21 '08 at 09:32
  • Take a look at this from ayende http://ayende.com/Blog/archive/2007/06/20/Did-you-know-Find-out-if-an-exception-was-thrown.aspx – Simon Laroche Nov 24 '08 at 03:20

10 Answers10

26

You can extend IDisposable with method Complete and use pattern like that:

using (MyWrapper wrapper = new MyWrapper())
{
    throw new Exception("Bad error.");
    wrapper.Complete();
}

If an exception is thrown inside the using statement Complete will not be called before Dispose.

If you want to know what exact exception is thrown, then subscribe on AppDomain.CurrentDomain.FirstChanceException event and store last thrown exception in ThreadLocal<Exception> variable.

Such pattern implemented in TransactionScope class.

Kelqualyn
  • 489
  • 5
  • 7
21

No, there is no way to do this in the .Net framework, you cannot figure out the current-exception-which-is-being-thrown in a finally clause.

See this post on my blog, for a comparison with a similar pattern in Ruby, it highlights the gaps I think exist with the IDisposable pattern.

Ayende has a trick that will allow you to detect an exception happened, however, it will not tell you which exception it was.

Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • Good catch on the "detect an exception happened" site that the hack doesn't work with medium trust asp.net. – TamusJRoyce Aug 02 '11 at 19:10
  • 1
    Unfortuantelly the future of `Marshal.GetExceptionCode()` isn't bright: _GetExceptionCode() may be unavailable in future releases._ [Marshal.GetExceptionCode Method](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getexceptioncode?view=net-7.0) – t3chb0t Feb 03 '23 at 18:17
6

It is not possible to capture the Exception in the Dispose() method.

However, it is possible to check Marshal.GetExceptionCode() in the Dispose to detect if an Exception did occur, but I wouldn't rely on that.

If you don't need a class and want just want to capture the Exception, you can create a function that accepts a lambda that is executed within 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 example, You can use a method that automatically does a Commit() or Rollback() of a transaction and do some logging. At this way you don't 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;
    }
}
Jo VdB
  • 2,016
  • 18
  • 16
  • 2
    I wish `Dispose` had been defined to take a parameter of type `Exception` which would indicate what, if any, exception was pending for the try/finally context (if any) *guarding the object*. Inquiring whether there is an exception pending for the thread isn't quite the same thing, since a `Dispose` might itself be invoked in a try/finally block that's nested within the one guarding the object to be disposed. – supercat Dec 31 '14 at 20:36
3

James, All wrapper can do is log it's own exceptions. You can't force the consumer of wrapper to log their own exceptions. That's not what IDisposable is for. IDisposable is meant for semi-deterministic release of resources for an object. Writing correct IDisposable code is not trivial.

In fact, the consumer of the class isn't even required to call your classes dispose method, nor are they required to use a using block, so it all rather breaks down.

If you look at it from the point of view of the wrapper class, why should it care that it was present inside a using block and there was an exception? What knowledge does that bring? Is it a security risk to have 3rd party code privy to exception details and stack trace? What can wrapper do if there is a divide-by-zero in a calculation?

The only way to log exceptions, irrespective of IDisposable, is try-catch and then to 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 creating an external API. You would have to wrap every call at your API's public boundary with try-catch in order to log that the exception came from your code.

If you're writing a public API then you really 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 them, I have seen IDisposable used for other interesting patterns:

  1. Auto-rollback transaction semantics. The transaction class would rollback the transaction on Dispose if not already committed.
  2. 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 be achieved with another layer of indirection and anonymous delegates easily and without having to overload IDisposable semantics. The important note is that your IDisposable wrapper is useless if you or a team member forget to use it properly.

Robert Paulson
  • 17,603
  • 5
  • 34
  • 53
  • 1
    Auto-rollback transaction is the main reason I'm interested in this. Essentially so I can have a using block and not have to explicitly comit at the end, but still allow it to abort on exception. I'm in two minds whether it is better to be explicit or not. MS go with explicit for System.Transactions, however I feel it's possible for developers to forget the comit. – tjmoore May 20 '10 at 10:49
  • Well, @tjmoore, you'll notice I said Auto-Rollback, not Auto-Commit. So in this scenario, the wrappers Dispose() method would rollback if not explicitly committed. However, I don't advocate this pattern as it's a bastardisation of IDisposable. With the ability to pass anonymous delegates as parameters to methods, you could easily introduce another layer of indirection to achieve the same results in a much more appropriate manner. – Robert Paulson May 20 '10 at 22:33
  • @tjmoore: If a commit should be mandatory, leaving the `using` block without calling it should fire an exception *unless* one is leaving the block because an exception is thrown in which case destroying the first exception by throwing another would be very rude. – supercat Apr 26 '18 at 23:11
2

You can do this buy implementing the Dispose method for the "MyWrapper" class. In the dispose method you can check to see if there 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");
    }
}
sammyboy
  • 31
  • 2
1

Instead of the syntactic sugar of the using statement, why not just implement your own logic for this. Something like:

try
{
  MyWrapper wrapper = new MyWrapper();

}
catch (Exception e)
{
  wrapper.CaughtException = true;
}
finally
{
   if (wrapper != null)
   {
      wrapper.Dispose();
   }
}
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • I think your overall suggestion is good, but your specific implementation won't catch an exception thrown in the Dispose() method, You'll need to wrap the contents of the finally block (or just the Dispose() call) inside it's own try/catch block. – Michael Burr Oct 21 '08 at 00:04
  • @MikeB the wrapper class will know if it had an exception. It can, if needed, implement a try-catch internally. – Robert Paulson Oct 21 '08 at 01:31
  • MyWrapper should be instantiated outside of the try/catch block or the try/catch should be rewritten differently. If MyWrapper throws an exception, you have a possible NullReferenceException in the catch block. – Richard Nienaber Oct 21 '08 at 04:41
  • @Robert Paulson - right. I misunderstood the original question. – Michael Burr Oct 21 '08 at 07:59
  • @MikeB You are right the first time. I want a way to find out about whether an exception was thrown in the code nested inside the using statement. I also want to preserve the nice using syntax since this is for an external API. – James Newton-King Oct 21 '08 at 09:29
1

In my case, I wanted to do this to log when an microservice crashes. I already have in place a using to properly clean up right before an instance shut down, but if that's because of an exception I want to see why, and I hate no for an answer.

Instead of trying to make it work in Dispose(), perhaps make a delegate for the work you need to do, and then wrap your exception-capturing in there. So in my MyWrapper logger, I add a method that 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 on what you need to do, this may be too restrictive, but it worked for what I needed.

Chad Hedgcock
  • 11,125
  • 3
  • 36
  • 44
1

Now, in 2017, this is the generic way to 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 you call it like this:

        cnn.WithinTransaction(
            transaction =>
            {
                var affected = ..sqlcalls..(cnn, ...,  transaction);
                return affected;
            });
mattias
  • 870
  • 5
  • 18
0

This will catch exceptions thrown either 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 this is relying on them using this this code - it sounds like you want MyWrapper to do that instead.

The using statement is just to make sure that Dispose always gets called. It's really doing this:

MyWrapper wrapper;
try
{
    wrapper = new MyWrapper();
}
finally {
    if( wrapper != null )
        wrapper.Dispose();
}

It sounds 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 with this in your implementation of Dispose - you should handle any issues during Disposal anyway.

If you're tying up some resource where you need users of your API to free it in some way consider having a Close() method. Your dispose should call it too (if it hasn't been already) but users of your API could also call it themselves if they needed finer control.

Keith
  • 150,284
  • 78
  • 298
  • 434
0

If you want to stay purely within .net, two approaches I would suggest would be writing a "try-catch-finally" wrapper, which would accept delegates for the different parts, or else writing a "using-style" wrapper, which accept a method to be invoked, along with one or more IDisposable objects which should be disposed after it completes.

A "using-style" wrapper could handle the disposal in a try-catch block and, if any exceptions are thrown in disposal, either wrap them in a CleanupFailureException which would hold the disposal failures as well as any exception that occurred in the main delegate, or else add something to the exception's "Data" property with the original exception. I would favor wrapping things in a CleanupFailureException, since an exception that occurs during cleanup will generally indicate a much bigger problem than one which occurs in main-line processing; further, a CleanupFailureException could be written to include multiple nested exceptions (if there are 'n' IDisposable objects, there could be n+1 nested exceptions: one from the mainline and one from each Dispose).

A "try-catch-finally" wrapper written in vb.net, while callable from C#, could include some features that are otherwise unavailable in C#, including the ability to expand it to a "try-filter-catch-fault-finally" block, where the "filter" code would be executed before the stack is unwound from an exception and determine whether the exception should be caught, the "fault" block would contain code that would only run if an exception occurred, but would not actually catch it, and both the "fault" and "finally" blocks would receive parameters indicating both what exception (if any) occurred during the execution of the "try", and whether the "try" completed successfully (note, btw, that it would be possible for the exception parameter to be non-null even if the mainline completes; pure C# code couldn't detect such a condition, but the vb.net wrapper could).

supercat
  • 77,689
  • 9
  • 166
  • 211