Friday, February 02, 2007

Rethrow an exception using throw;

It is not uncommon that you want to rethrow an exception in a catch-block. This happens if you need to cleanup some state in the catch-handler, but you still want to inform the application that something has gone wrong.

You often see this implemented as such:

static void SomeMethod()
{
    try
    {
        MethodThatCanThrowAnException();
    }
    catch (Exception ex)
    {
        // Do stuff (cleanup)
        throw ex; // WRONG - DO NOT USE THIS!
    }
}

There is a subtle problem with this: rethrowing the existing exception-object will reset its stacktrace. Whoever catches this exception and examines the stacktrace will think that it was caused by your code.

It is better to do this:

static void SomeMethod()
{
    try
    {
        MethodThatCanThrowAnException();
    }
    catch (Exception ex)
    {
        // Do stuff (cleanup)
        throw; // does not reset the stacktrace
    }
}

Now whoever catches this exception will see the complete stacktrace and can see the originator who caused the exception. This might save a lot of time for whoever has to debug the problem.

 

Another valid possibility is to throw a new exception, but set its InnerException propery to the original exception like this:

catch (NullReferenceException ex)
{
    // Do stuff (cleanup)
    throw new ObjectNotInitializedException(ex);
}
Whoever catches this exception can view a separate stacktrace for your exception and the InnerException. I would use this second pattern if the new exception that you throw contains more information about the cause of the problem than the inner-exception. For instance if the inner-exception is a NullReferenceException, your exception can give more context (e.g. the object is not yet initialized).

Kristof

6 comments:

Anonymous said...

Actually, throw; does modify the stack trace slightly - the top frame, to be exact. Code below demonstrates. It can be slightly annoying.

using System;

public class C
{
public static void Main(string[] args) {
try
{
Rethrow();
}
catch (Exception e)
{
Console.Out.WriteLine("Rethrown exception is " + e);
// output original stack trace except the Rethrow() stack frame frame now indicates the line where the exception was rethrown rather than the line where the exception was thrown
}
Console.ReadKey();
}

static void Rethrow()
{
try
{
throw new Exception();
}
catch (Exception e)
{
Console.Out.WriteLine("Original exception is " + e); // outputs original stack trace
throw;
}
}
}

Anonymous said...

I have just seen it do exactly what "anonymous" is describing - i.e. it changes the stack trace to show the location of "throw;". I'm sure it didn't used to do this in dot net 2.0 - is there a setting somewhere that affects this? Or is it an "improvement" in dot net 3.5. I'd much prefer to see where the actual exception came from, rather than the location of the re-throw.

Anonymous said...

See this article... http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack-trace.aspx

Anonymous said...

Me again... here's a post from someone on the .NET runtime team, stating that "throw;" does *not* modify the stack trace. So I think something has definitely changed.

roe land said...

Here is the fix I converted to an exception extension method:

/// <summary>
/// Invokes the exception's InternalPreserveStackTrace() to allow for rethrows
/// </summary>
/// <param name="exc">the exception to rethrow</param>
/// <returns>the exception ready to rethrow</returns>
/// <example>
///     <code>
///     try
///     {
///         //…
///     }
///     catch( Exception exc )
///     {
///         //…
///         throw exc.PreserveStackTrace(); //rethrow the exception – preserving the full call stack trace!
///     }
///     </code>
/// </example>
public static Exception PreserveStackTrace( this Exception exc )
{ //see <http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack-trace.aspx>
    typeof( Exception ).GetMethod( "InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic ).Invoke( exc, null );
    return exc;
}

dawninghu said...

nice post.