C# “为什么?”;扔;及;抛出“ex”;在catch块中,行为是否相同?

C# “为什么?”;扔;及;抛出“ex”;在catch块中,行为是否相同?,c#,C#,我了解到,当在catch块中时,我可以使用“throw;”或“throw ex;”重新显示当前异常 发件人: 要保留包含异常的原始堆栈跟踪信息,请在不指定异常的情况下使用throw语句 但是当我试着用这个 try{ try{ try{ throw new Exception("test"); // 13 }catch (Exception ex1){

我了解到,当在catch块中时,我可以使用“throw;”或“throw ex;”重新显示当前异常

发件人:

要保留包含异常的原始堆栈跟踪信息,请在不指定异常的情况下使用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());
        }
我有三个不同的堆栈。我原以为第一道和第二道是一样的。我做错了什么?(还是理解错误?)

系统异常:测试 在c:\Program.cs:第13行中的ConsoleApplication1.Program.Main(字符串[]args)处 系统异常:测试 在c:\Program.cs:第16行中的ConsoleApplication1.Program.Main(字符串[]args)处 系统异常:测试
在控制台c:\Program.cs中的Application 1.Program.Main(字符串[]args):第20行

throw
仅当您不从当前堆栈帧中抛出堆栈帧时,才会保留堆栈帧。通过用一种方法完成所有这些,你就可以做到这一点

看看这个答案:


PS:+1,用于提出一个实际上是有效问题的问题。

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

我在这个问题上做了更多的研究,以下是我个人的结论:

永远不要使用“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
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
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
直观地说,Java程序员会认为这两种异常是相同的:

System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
但C#文档清楚地表明,这正是我们所期望的:

如果通过在throw语句中指定异常来重新引发异常,则堆栈跟踪将在当前方法处重新启动,并且引发异常的原始方法和当前方法之间的方法调用列表将丢失。若要保留包含异常的原始堆栈跟踪信息,请使用不带spe的throw语句这是一个例外。”

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

收益率:

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
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
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
显然这不是我所期望的(因为这是最初的问题)。我在第二个堆栈跟踪中丢失了原始抛出点。Simon Whitehead链接了解释,即该抛出;仅当当前方法中没有发生异常时才保留堆栈跟踪。因此“抛出”在同一个方法中没有参数是非常无用的,因为一般来说,它不会帮助您找到异常的原因

与任何Java程序员一样,我将第17行的语句替换为:

throw new Exception("rethrow", ex);// 17
收益率:

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
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
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
此观察结果与前面的观察结果类似:堆栈跟踪没有为当前方法范围保留,我再次丢失了发生异常的被调用方法。例如,如果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()的调用引发了异常?堆栈说是第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

我对这一切的简单结论是永远不要使用“扔”但是,要始终根据指定的原因重新抛出新异常。

是的,请再次阅读该语句。要保留堆栈跟踪do
throw
而不是
throw ex2
各位,请阅读实际代码。在一种情况下,OP只使用
throw
,在另一种情况下,他们使用
throw ex
。但是,在所有三种情况下(也就是说,不仅仅是
throw ex
)的情况,还有三种不同的堆栈(假定)。如果堆栈跟踪确实不同,那么这确实是一个有趣的问题。@mitch如果你阅读了这个问题,那么文档中的内容和我观察到的内容是不同的!这就是我想澄清的。如果我用“throw”重述,为什么ex1和ex2堆栈不同?为什么投票结束?对我来说似乎是一个有效的问题。@AndrewCooper:因为人们没有阅读整个问题,并且认为OP不理解文档或没有阅读文档。讽刺的是,谢谢!但是你注意到你的堆栈内容了吗?第1行被保留,但第2行不同(在第一和第二堆栈跟踪中)。我也没想到会出现这种情况。我想这可以追溯到Simon的答案。但我认为这对于调试来说更好,因为您知道在异常成为问题之前代码中执行的最后一个位置。