Re-throwing an exception is a little more complex that I had realized. The gotcha, pointed out by helpful reader "Sander", is that when the initial exception is thrown from directly inside the try block, rather than from within a method call inside the try block, you will lose the line number of the faulty line of code, regardless of how you re-throw the exception. Here's some sample code that demonstrates what I mean:
using System;
namespace RethrowExTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Whoops! Re-throw from procedural code loses the line number of the exception");
try
{
try
{
int zero = 0;
int i = 1 / zero;
}
catch (Exception ex)
{
Console.WriteLine("Inner catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw;
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Console.WriteLine();
Console.WriteLine("However, re-throw from exception-throwing method retains the line number, as expected");
try
{
try
{
MethodThatThrowsException();
}
catch (Exception ex)
{
Console.WriteLine("Inner catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw;
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Console.WriteLine();
Console.WriteLine("New throw from procedural code loses the line number, as expected");
try
{
try
{
int zero = 0;
int i = 1 / zero;
}
catch (Exception ex)
{
Console.WriteLine("Inner catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw ex;
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Console.WriteLine();
Console.WriteLine("New throw from method loses the line number, as expected");
try
{
try
{
MethodThatThrowsException();
}
catch (Exception ex)
{
Console.WriteLine("Inner catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw ex;
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch:");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
}
private static void MethodThatThrowsException()
{
int zero = 0;
int i = 1 / zero;
}
}
}
The output looks just like what we expected:
Whoops! Re-throw from procedural code loses the line number of the exception
Inner catch:
Attempted to divide by zero.
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 15
Outer catch:
Attempted to divide by zero.
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 22
However, re-throw from exception-throwing method retains the line number, as expected
Inner catch:
Attempted to divide by zero.
at RethrowExTest.Program.MethodThatThrowsException() in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 103
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 37
Outer catch:
Attempted to divide by zero.
at RethrowExTest.Program.MethodThatThrowsException() in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 103
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 44
New throw from procedural code loses the line number, as expected
Inner catch:
Attempted to divide by zero.
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 60
Outer catch:
Attempted to divide by zero.
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 67
New throw from method loses the line number, as expected
Inner catch:
Attempted to divide by zero.
at RethrowExTest.Program.MethodThatThrowsException() in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 103
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 82
Outer catch:
Attempted to divide by zero.
at RethrowExTest.Program.Main(String[] args) in C:\Projects\Tests\RethrowExceptions\RethrowExTest\Program.cs:line 89
How should we design our exception handling, then? Follow these 2 simple rules:
1. If you can reasonably expect any exceptions to be thrown only by methods called from within the try block, you can just call "throw" instead of "throw ex" and retain the debugging info you need. This is what I explained in the previous article.
2. If on the other hand there is a substantial probability of the exception being thrown directly from within the try block, you should log the exception's stack trace before you re-throw it.
P.S. I always welcome comments from knowledgeable readers like "Sanders." Thanks, man!