Reputation: 4497
I read that when in a catch block, I can rethrow the current exception using "throw;" or "throw ex;".
From: http://msdn.microsoft.com/en-us/library/ms182363%28VS.80%29.aspx
"To keep the original stack trace information with the exception, use the throw statement without specifying the exception."
But when I try this with
try{
try{
try{
throw new Exception("test"); // 13
}catch (Exception ex1){
Console.WriteLine(ex1.ToString());
throw; // 16
}
}catch (Exception ex2){
Console.WriteLine(ex2.ToString()); // expected same stack trace
throw ex2; // 20
}
}catch (Exception ex3){
Console.WriteLine(ex3.ToString());
}
I get three different stacks. I was expecting the first and second trace to be the same. What am I doing wrong? (or understanding wrong?)
System.Exception: test at ConsoleApplication1.Program.Main(String[] args) in c:\Program.cs:line 13 System.Exception: test at ConsoleApplication1.Program.Main(String[] args) in c:\Program.cs:line 16 System.Exception: test at ConsoleApplication1.Program.Main(String[] args) in c:\Program.cs:line 20
Upvotes: 13
Views: 703
Reputation: 4497
Well I dug a bit more on this issue and here is my very personal conclusion:
Never use “throw;” but always rethrow a new Exception with the cause specified.
Here is my reasoning:
I was influenced by my previous experience with Java and expected C# throw to be very similar to that of Java. Well I dug a bit more on this issue and here are my observations:
static void Main(string[] args){
try {
try {
throw new Exception("test"); // 13
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw ex;// 17
}
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
}
Yields:
System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 17
Intuitively, a Java programmer would have expected both exceptions to be the same:
System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
But the C# documentation clearly says that this is what is to be expected:
“If an exception is re-thrown by specifying the exception in the throw statement, the stack trace is restarted at the current method and the list of method calls between the original method that threw the exception and the current method is lost. To keep the original stack trace information with the exception, use the throw statement without specifying the exception.”
Now if I slightly changed the test (replacing throw ex; by throw; on line 17).
try {
try {
throw new Exception("test"); // 13
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw;// 17
}
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
Yields:
System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 17
Obviously this is not what I expected (as this was the original question). I lost the original throw point in the second stack trace. Simon Whitehead linked the explanation, which is that throw; preserves the stack trace only if the exception did not occur in the current method. So “throw” without parameter in the same method is pretty useless as, in general, it will not help you find the cause of the exception.
Doing what any Java programmer would do, I replaced the statement on line 17 by:
throw new Exception("rethrow", ex);// 17
Yields:
System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
System.Exception: rethrow ---> System.Exception: test
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
--- End of inner exception stack trace ---
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 17
which is a much better result.
Then, I started testing with method calls.
private static void throwIt() {
throw new Exception("Test"); // 10
}
private static void rethrow(){
try{
throwIt(); // 15
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw; // 18
}
}
static void Main(string[] args){
try{
rethrow(); // 24
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
}
Again, the stack traces were not what I (a Java programmer) expected. In Java, both stacks would have been the same and three method deep as:
java.lang.Exception: Test
at com.example.Test.throwIt(Test.java:10)
at com.example.Test.rethrow(Test.java:15)
at com.example.Test.main(Test.java:24)
The first stack trace is only two method deep.
System.Exception: Test
at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
at ConsoleApplication1.Program.rethrow() in Program.cs:line 15
It is like if the stack trace was being populated as part of the stack unwinding process. If I was to log the stack trace to investigate the exception at that point, I’d probably be missing vital information.
The second stack trace is three method deep but line 18 (throw;) appears in it.
System.Exception: Test
at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
at ConsoleApplication1.Program.rethrow() in Program.cs:line 18
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 24
This observation is similar to the one made earlier: the stack trace is not preserved for the current method scope and again I loose the called method in which the exception occurs. For example, if rethow was written as:
private static void rethrow(){
try{
if (test)
throwIt(); // 15
else
throwIt(); // 17
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw; // 20
}
}
Yields
System.Exception: Test
at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
at ConsoleApplication1.Program.rethrow() in Program.cs:line 20
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 26
Which call to throwIt() threw the Exception? Stack says line 20 so is it line 15 or 17?
The solution is the same as the previous: wrap the cause in a new exception which yields:
System.Exception: rethrow ---> System.Exception: Test
at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
at ConsoleApplication1.Program.rethrow(Boolean test) in Program.cs:line 17
--- End of inner exception stack trace ---
at ConsoleApplication1.Program.rethrow(Boolean test) in Program.cs:line 20
at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 26
My simple conclusion to all this is to never use “throw;” but to always rethrow a new exception with the cause specified.
Upvotes: 2
Reputation: 32596
Simon beat me to answering, but you can see the expected behaviour simply by throwing the original exception from inside another function:
static void Main(string[] args)
{
try
{
try
{
try
{
Foo();
}
catch (Exception ex1)
{
Console.WriteLine(ex1.ToString());
throw;
}
}
catch (Exception ex2)
{
Console.WriteLine(ex2.ToString()); // expected same stack trace
throw ex2;
}
}
catch (Exception ex3)
{
Console.WriteLine(ex3.ToString());
}
}
static void Foo()
{
throw new Exception("Test2");
}
Upvotes: 2
Reputation: 65077
throw
will only preserve the stack frame if you don't throw it from within the current one. You're doing just that by doing all of this in one method.
See this answer: https://stackoverflow.com/a/5154318/1517578
PS: +1 for asking a question that was actually a valid question.
Upvotes: 7