Wednesday, August 19, 2009

Rethrowing an Exception without resetting the Stack Trace.

No comments:
Source code: http://www.box.net/shared/kjkgq36itq

Exception handling in .NET is a complicated subject. It's complicated and always spawns all kinds of debates. I won't post my opinion or anything like that, but I want to point out a subtle yet important difference when re-throwing an exception. The two ways you can re-throw an exception are:



try
{
DoSomeExceptionThrowingMethod();
}
catch (Exception ex)
{

throw ex;
}


try
{
DoSomeExceptionThrowingMethod();
}
catch (Exception ex)
{

throw;
}


As you can see, the only difference is in the catch block where I'm calling throw. In the first case I'm calling throw ex, while in the second case I'm simply calling throw. What's the difference?

Well when you're simply calling throw, you're effectivley calling "rethrow" meaning "re throw the exception you just caught". When you're calling "throw ex" you're basically just saying "throw" and you're not rethrowing the exception you just caught. That doesn't make much sense, so let's whip up a code sample:

First, we'll have a class that has one method which throws an exception:



public class ExceptionThrower
{
public void InvalidMethod()
{
throw new InvalidOperationException("This method is invalid.");
}
}


Now, we'll add a few layers of method calling to this demo so we can make the actual stack trace a little bigger (I'll explain more soon):



public class Layer1
{
private ExceptionThrower thrower;

public Layer1()
{
this.thrower = new ExceptionThrower();
}

public void Layer1Method()
{
this.thrower.InvalidMethod();
}
}


Here's one layer of method calling, now we'll add one more:



public class Layer2
{
private Layer1 layer1;

public Layer2()
{
this.layer1 = new Layer1();
}

public void Layer2Method()
{
this.layer1.Layer1Method();
}
}


These classes aren't doing much more than just wrapping the method calls from the object they have internally.

Now, let's code up our main method:



class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Calling KeepStackTrace");
KeepStackTrace();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.StackTrace);
}

try
{
Console.WriteLine("Calling ResetStackTrace");
ResetStackTrace();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.StackTrace);
}

Console.ReadKey(true);
}

private static void KeepStackTrace()
{
Layer2 l2 = new Layer2();
try
{
l2.Layer2Method();
}
catch (InvalidOperationException ex)
{
throw;
}
}

private static void ResetStackTrace()
{
Layer2 l2 = new Layer2();
try
{
l2.Layer2Method();
}
catch (InvalidOperationException ex)
{
throw ex;
}
}
}


So basically we have two methods that call into our Layer2 class. One wraps the method in a try / catch but just calls throw, while the other calls throw ex. In the main method, we output the Stack Trace of the exception. Let's examine the output:

Calling KeepStackTrace

at ExceptionReThrow.ExceptionThrower.InvalidMethod() in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\ExceptionThrower.cs:line 12
at ExceptionReThrow.Layer1.Layer1Method() in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\Layer1.cs:line 19
at ExceptionReThrow.Layer2.Layer2Method() in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\Layer2.cs:line 19
at ExceptionReThrow.Program.KeepStackTrace() in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\Program.cs:line 48
at ExceptionReThrow.Program.Main(String[] args) in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\Program.cs:line 19

Calling ResetStackTrace

at ExceptionReThrow.Program.ResetStackTrace() in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\Program.cs:line 61
at ExceptionReThrow.Program.Main(String[] args) in C:\Users\Alex\Documents\Visual Studio 2008\Projects\ExceptionReThrow\ExceptionReThrow\Program.cs:line 29


As you can tell, when calling the KeepStackTrace which simply did "throw" the entire stack trace with all the layers are kept and we can see it all the way down to where the exception originated.

When calling ResetStrackTrace though, the method where we do "throw ex", you'll notice that all we see is down till our ResetStackTrace method. We don't see anything past that, even though that's not really where the exception originated.

Bottom line, for the most part, this isn't all that relevant because you really shouldn't be catching exceptions if you don't plan on doing anything with it. However, if you do want to at least log it, but let it bubble up, be sure to do "throw" so that when the Exception does finally bubble to the top, you have the entire stack trace.