在catch块中,“throw”和“throw ex”为什么表现相同?

13

我读到,在catch块中,可以使用"throw;"或"throw ex;"重新抛出当前异常。

来自:http://msdn.microsoft.com/en-us/library/ms182363%28VS.80%29.aspx

"要保留异常的原始堆栈跟踪信息,请使用throw语句而不指定异常。"

但是当我尝试这样做时:

        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());
        }

我得到了三个不同的堆栈。我本来期望第一个和第二个跟踪是相同的。我错在哪里了? (或者理解错了?)

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


是的,请再次阅读该语句。要保留堆栈跟踪,请使用 throw 而不是 throw ex2 - jb.
1
@mitch 如果您仔细阅读问题,文档说明和我的观察结果是不同的!这就是我想要澄清的事情。如果我使用“throw”重新抛出异常,为什么ex1和ex2堆栈会不同呢? - cquezel
2
为什么要关闭投票?在我看来,这似乎是一个有效的问题。 - Andrew Cooper
1
@AndrewCooper:因为人们没有读完整个问题,就假设OP不理解文档或者没有阅读它。真是讽刺。 - siride
@siride:由于许多问题的“OP没有理解文档或没有阅读文档”,变得愤世嫉俗的症状。我也有罪。 - Mitch Wheat
显示剩余2条评论
3个回答

7
throw只有在你不是在当前堆栈帧内抛出它时才会保留堆栈帧。你正在一个方法中执行所有操作,因此正在从当前堆栈帧中抛出它。

请参见此答案:https://dev59.com/am435IYBdhLWcg3w4Uas#5154318

附言:感谢您提出一个真正有效的问题,给您点赞。


2

虽然Simon已经回答了,但你可以通过在另一个函数内部抛出原始异常来查看预期的行为:

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");
}

谢谢!但是你注意到栈的内容了吗?第一行被保留,但第二行不同(在第一个和第二个堆栈跟踪中)。我也没有预料到这一点。 - cquezel
我猜这与Simon的回答有关。但我认为这对于调试来说更好,因为你知道在异常实际成为问题之前,代码中最后执行的位置。 - Andrew Cooper

2

我对这个问题进行了一些深入调查,得出了以下个人结论:

永远不要使用“throw;”,而是总是重新生成一个指定原因的新异常。

这是我的推理:

我受到之前在Java方面的经验影响,并预期C# throw与Java非常相似。好吧,我对此问题进行了更深入的挖掘,以下是我的观察:

    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());
        }
    }

得到的结果:

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

直观上,Java程序员会期望这两个异常是相同的:

System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13

但是C#文档清楚地指出了可以预期的行为:
“如果通过在throw语句中指定异常来重新抛出异常,则堆栈跟踪将从当前方法重新开始,并且在原始引发异常的方法与当前方法之间调用的方法列表将丢失。要将原始堆栈跟踪信息保留到异常中,请使用不指定异常的throw语句。”
现在,如果我稍微更改测试(将第17行的throw ex;替换为throw;)。
        try {
            try {
                throw new Exception("test"); // 13
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString());
                throw;// 17
            }
        } catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }

产生:

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

显然,这不是我预期的结果(因为这是最初的问题)。在第二个堆栈跟踪中,我丢失了原始抛出点。Simon Whitehead提供了解释,即throw;仅在异常未发生在当前方法中时保留堆栈跟踪。因此,“throw”在同一方法中没有参数是相当无用的,通常它不会帮助您找到异常的原因。
像任何Java程序员一样,我将第17行的语句替换为:
throw new Exception("rethrow", ex);// 17

产生:

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

这是一个更好的结果。

然后,我开始测试方法调用。

    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());
        }
    }

又一次,堆栈跟踪并不是我(一个Java程序员)所期望的。在Java中,两个堆栈都应该相同,并且深度为三个方法:

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)

第一个堆栈跟踪仅有两个方法深度。

System.Exception: Test
  at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
  at ConsoleApplication1.Program.rethrow() in Program.cs:line 15

就好像堆栈跟踪是作为堆栈展开过程的一部分而被填充的。如果我在那个时候记录堆栈跟踪以调查异常,我可能会丢失关键信息。

第二个堆栈跟踪只有三个方法深度,但第18行(throw;)也出现在其中。

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

这个观察结果与之前的相似:堆栈跟踪不会在当前方法范围内保留,我再次失去了异常发生的调用方法。例如,如果rethrow被写成:

private static void rethrow(){
    try{
        if (test) 
            throwIt(); // 15
        else 
            throwIt(); // 17
    } catch (Exception ex) {
        Console.WriteLine(ex.ToString());
        throw; // 20
    }
}

产量

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

哪个调用throwIt()方法抛出了异常?堆栈显示第20行,那么是第15行还是第17行?

解决方案与之前相同:将原因包装在一个新的异常中,即可得到:

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

我的简单结论是永远不要使用“throw;”,而是始终重新抛出一个指定原因的新异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接