I was unaware of this behaviour and find it quite suprising. It certainly looks as if throw is behaving incorrectly but it isn't and we can see that more clearly if we move the exception source and the catch-rethrow into different methods.
It is important to understand that the line number reported in a stack frame is the point where control leaves the method and this may not be the point where the exception was first thrown. In the special case of catch-rethrow the stack frame line number corresponds to the position of the rethrow and this happens for both
throw
and
throw ex
.
Here is simple program that demonstrates the way that line numbers can change within the stack frame.
class Program {
static void Main(string[] args) {
Console.BufferWidth = 160;
Console.WriteLine("Indirect call of BadMethod with no rethrows");
Console.WriteLine("*******************************************");
try {
#line 100
PassThrough();
#line default
} catch (Exception e) {
Console.WriteLine(e);
}
Console.WriteLine();
Console.WriteLine("Indirect call of BadMethod with rethrow");
Console.WriteLine("***************************************");
try {
#line 200
WithRethrow();
#line default
} catch (Exception e) {
Console.WriteLine(e);
}
Console.WriteLine();
Console.Read();
}
public static void PassThrough() {
#line 1000
BadMethod();
#line default
}
public static void WithRethrow() {
try {
#line 2000
BadMethod();
} catch (Exception e) {
Console.WriteLine(e);
throw;
}
#line default
}
public static void BadMethod() {
#line 3000
throw new Exception("\"MSG: Good method gone bad\"");
#line default
}
}
The strange
#line
directives alter the line numbers saved in debug information and give us simple numbers in the stack traces. This was done to make discussion of the results easier.
There are two cases and in the both we call the exception throwing BadMethod() via an intermediary, either PassThrough() which does nothing or WithRethrow() which does what the name suggests.
Indirect call of BadMethod with no rethrows
*******************************************
System.Exception: "MSG: Good method gone bad"
at BadMethod() line 3000
at PassThrough() line 1000
at Main(String[] args) line 100
Indirect call of BadMethod with rethrow
***************************************
System.Exception: "MSG: Good method gone bad"
at BadMethod() line 3000
at WithRethrow() line 2000
System.Exception: "MSG: Good method gone bad"
at BadMethod() line 3000
at WithRethrow() line 2003
at Main(String[] args) line 200
If you can imagine an exception passing through a method in the search for a matching catch block then in PassThrough() the exception enters and leaves at the same line number (1000) and this is reported in the stack frame.
In the WithRethrow() method the exception enters at line 2000 but leaves at line 2003 after being caught and rethrown. Comparison of the stack trace obtained within WithRethrow() with that from Main() clearly show the change of reported line number.
This is exactly analogous to the situation that you originally described, i.e.
public void WTFMethod() {
try {
throw new Exception();
} catch {
throw;
}
}
The exception is thrown, caught and then rethrown and the stack frame reports the point where control left the method. So although it looks very odd not to get the correct location for the exception, the behaviour for a catch and rethrow is completely consistent through the whole call stack.
Alan.