好吧,我在这个问题上挖掘了更多,这是我非常个人的结论:
永远不要使用“投掷”;但总是以指定的原因重新抛出一个新的异常。
这是我的推理:
我受到我之前使用 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 语句而不指定异常。”</p>
现在,如果我稍微改变了测试(替换 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
我对这一切的简单结论是永远不要使用“投掷”;但总是重新抛出具有指定原因的新异常。