cquezel
cquezel

Reputation: 4497

Why do "throw" and "throw ex" in a catch block behave the same way?

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

Answers (3)

cquezel
cquezel

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

Andrew Cooper
Andrew Cooper

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

Simon Whitehead
Simon Whitehead

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

Related Questions