What is the proper way to re-throw an exception in C#?
我有一个问题要问你,那是因为我的搭档以不同于我的方式做事。
这样做更好吗:
1 2 3 4 5 6 7 8 9 | try { ... } catch (Exception ex) { ... throw; } |
或者:
1 2 3 4 5 6 7 8 9 | try { ... } catch (Exception ex) { ... throw ex; } |
他们做同样的事吗?一个比另一个好吗?
您应该始终使用以下语法来重新引发异常,否则将跺脚堆栈跟踪:
1 | throw; |
如果打印"throw ex"产生的跟踪,您将看到它以该语句结束,而不是以异常的真正来源结束。
基本上,使用"throw ex"应被视为刑事犯罪。
我的偏好是使用
1 2 3 4 5 6 7 8 |
这将保留原始错误,但允许您放置更多上下文,如对象ID、连接字符串等。通常,我的异常报告工具将有5个链接的异常要报告,每个异常报告更详细。
如果抛出一个没有变量的异常(第二个示例),stacktrace将包含引发该异常的原始方法。
在第一个示例中,StackTrace将更改以反映当前方法。
例子:
1 2 3 4 5 6 7 8 | static string ReadAFile(string fileName) { string result = string.Empty; try { result = File.ReadAllLines(fileName); } catch(Exception ex) { throw ex; // This will show ReadAFile in the StackTrace throw; // This will show ReadAllLines in the StackTrace } |
第一个保留异常的原始堆栈跟踪,第二个将其替换为当前位置。
因此,第一个是迄今为止更好的。
我知道这是一个古老的问题,但我会回答它,因为我必须不同意这里所有的答案。
现在,我同意,大多数时候,你要么想做一个简单的
不过也有例外。在某些情况下,一个方法将调用另一个方法,并且导致内部调用异常的条件应被视为外部调用的相同异常。
一个例子是使用另一个集合实现的专门集合。假设是一个
如果有人在您的集合类中调用了
现在,你完全无法理解被训斥的意思,让它过去。在这里,尽管用户在调用
或者你可以自己检查:
1 2 3 4 5 6 7 8 9 10 | public CopyTo(T[] array, int arrayIndex) { if(array == null) throw new ArgumentNullException("array"); if(arrayIndex < 0) throw new ArgumentOutOfRangeException("arrayIndex","Array Index must be zero or greater."); if(Count > array.Length + arrayIndex) throw new ArgumentException("Not enough room in array to copy elements starting at index given."); _innerList.CopyTo(array, arrayIndex); } |
这并不是更糟糕的代码,因为它是样板文件,我们可能只需要从
我们可以检查并包装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentNullException ane) { throw new ArgumentNullException("array", ane); } catch(ArgumentOutOfRangeException aore) { throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore); } catch(ArgumentException ae) { throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae); } } |
就新添加的可能有问题的代码而言,情况更糟。我们并没有从内在的例外中得到什么。如果向此方法传递一个空数组并接收一个
在这里,我们可以随心所欲:
1 2 3 4 5 6 7 8 9 10 11 | public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentException ae) { throw ae; } } |
如果用户使用不正确的参数调用异常,我们期望必须抛出的每个异常都将被重新抛出正确抛出。如果在这里使用的逻辑中有一个bug,它在两行中的一行中——要么我们在决定这是一个这种方法工作的情况时出错,要么我们在让
现在。我仍然同意,大多数时候,您要么想要一个普通的
编辑:
我们也可以将两者结合起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentException ae) { throw ae; } catch(Exception ex) { //we weren't expecting this, there must be a bug in our code that put //us into an invalid state, and subsequently let this exception happen. LogException(ex); throw; } } |
应始终使用"throw;"重新引发.NET中的异常,
请参阅http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
基本上,msil(cil)有两条指令——"throw"和"rethrow"和"c"throw ex;"编译成msil的"throw"和"c"throw;"-编译成msil的"rethrow"!基本上,我可以看到为什么"throw-ex"会覆盖堆栈跟踪。
第一个更好。如果您尝试调试第二个并查看调用堆栈,您将看不到原始异常的来源。如果您真的需要重新发送,可以使用一些技巧来保持调用堆栈的完整性(尝试搜索,它以前已经被应答过)。
我发现,如果在捕获异常的同一方法中抛出异常,那么堆栈跟踪就不会被保留,这是值得的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void testExceptionHandling() { try { throw new ArithmeticException("illegal expression"); } catch (Exception ex) { throw; } finally { System.Diagnostics.Debug.WriteLine("finally called."); } } |
这要看情况而定。在调试构建中,我希望尽可能少地查看原始堆栈跟踪。在这种情况下,"扔";符合要求。然而,在一个发布版本中,(a)我想用包含原始堆栈跟踪的日志记录错误,一旦记录完成,(b)重新设置错误处理,以便对用户更有意义。这里"抛出异常"是有意义的。确实,重新引发错误会丢弃原始堆栈跟踪,但非开发人员从查看堆栈跟踪信息中一无所获,因此可以重新引发错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void TrySuspectMethod() { try { SuspectMethod(); } #if DEBUG catch { //Don't log error, let developer see //original stack trace easily throw; #else catch (Exception ex) { //Log error for developers and then //throw a error with a user-oriented message throw new Exception(String.Format ("Dear user, sorry but: {0}", ex.Message)); #endif } } |
问题的表达方式,点评"throw:"vs"throw ex";让它有点像是一条红鲱鱼。真正的选择是在"throw;"和"throw exception"之间,其中"throw ex;"是"throw exception"不太可能出现的特殊情况。