我对这个问题进行了一些深入调查,得出了以下个人结论:
永远不要使用“throw;”,而是总是重新生成一个指定原因的新异常。
这是我的推理:
我受到之前在Java方面的经验影响,并预期C# throw与Java非常相似。好吧,我对此问题进行了更深入的挖掘,以下是我的观察:
static void Main(string[] args){
try {
try {
throw new Exception("test");
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw ex;
}
} 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");
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw;
}
} 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");
}
private static void rethrow(){
try{
throwIt();
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw;
}
}
static void Main(string[] args){
try{
rethrow();
} 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();
else
throwIt();
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
throw;
}
}
产量
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;”,而是始终重新抛出一个指定原因的新异常。
throw
而不是throw ex2
。 - jb.