【问题标题】:Why do "throw" and "throw ex" in a catch block behave the same way?为什么 catch 块中的“throw”和“throw ex”的行为方式相同?
【发布时间】:2013-01-18 18:13:56
【问题描述】:

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

发件人: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: 测试 在 c:\Program.cs:line 13 中的 ConsoleApplication1.Program.Main(String[] args) System.Exception:测试 在 c:\Program.cs:line 16 中的 ConsoleApplication1.Program.Main(String[] args) System.Exception:测试 在 c:\Program.cs:line 20 中的 ConsoleApplication1.Program.Main(String[] args)

【问题讨论】:

  • 是的,再次阅读声明。保持堆栈跟踪做throw 而不是throw ex2
  • 各位,请阅读实际代码。在一种情况下,OP 只使用throw,而在另一种情况下,他们使用throw ex。然而,在所有 三个 的情况下(也就是说,不只是 throw ex 的情况),存在三个不同的堆栈(假定)。如果堆栈跟踪确实不同,那么这确实是一个有趣的问题。
  • @mitch 如果您阅读了这个问题,文档所说的和我观察到的不同!这就是我要澄清的。如果我用“throw”重新抛出,为什么 ex1 和 ex2 堆栈不同?
  • 为什么要关闭投票?对我来说似乎是一个有效的问题。
  • @AndrewCooper:因为人们没有阅读整个问题并认为 OP 不理解文档或没有阅读它。讽刺。

标签: c#


【解决方案1】:

throw 只会保留堆栈帧,如果您不从当前帧中抛出它。您可以通过一种方法完成所有这些操作。

看到这个答案:https://stackoverflow.com/a/5154318/1517578

PS:+1 表示提出的问题实际上是一个有效的问题。

【讨论】:

    【解决方案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");
    }
    

    【讨论】:

    • 谢谢!但是你注意到你的堆栈内容了吗?第 1 行被保留,但第 2 行不同(在第一个和第二个堆栈跟踪中)。我也没想到会这样。
    • 我想这可以追溯到西蒙的回答。但我认为这更适合调试,因为您知道代码中在异常实际成为问题之前执行的最后一个位置。
    【解决方案3】:

    嗯,我在这个问题上挖掘了更多,这是我非常个人的结论:

    永远不要使用“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 语句而不指定异常。”

    现在,如果我稍微更改测试(替换 throw ex; by throw; 在第 17 行)。

            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
    

    显然这不是我所期望的(因为这是最初的问题)。我在第二个堆栈跟踪中丢失了原始抛出点。西蒙怀特黑德将解释联系起来,即投掷;仅当当前方法中未发生异常时才保留堆栈跟踪。所以在同一个方法中不带参数的“抛出”是毫无用处的,一般来说,它不会帮助你找到异常的原因。

    做任何 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 行(抛出;)出现在其中。

    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
    

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

    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() 的哪个调用引发了异常? Stack 说第 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
    

    我对这一切的简单结论是永远不要使用“投掷”;但总是重新抛出具有指定原因的新异常。

    【讨论】:

      猜你喜欢
      • 2013-11-24
      • 2011-04-12
      • 2010-12-22
      • 2012-06-04
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 2014-03-18
      • 2015-03-05
      相关资源
      最近更新 更多