Best practices for catching and re-throwing .NET exceptions
捕获异常并重新抛出异常时,应考虑哪些最佳实践?我要确保保留
1 2 3 4 5 6 7 8 | try { //some code } catch (Exception ex) { throw ex; } |
Vs:
1 2 3 4 5 6 7 8 | try { //some code } catch { throw; } |
保存堆栈跟踪的方法是使用
1 2 3 4 5 6 | try { // something that bombs here } catch (Exception ex) { throw; } |
Mike也是正确的,假设异常允许您传递异常(建议这样做)。
卡尔·塞古在他编写电子书的基础上也写了一篇关于异常处理的伟大文章,这是一本伟大的著作。
编辑:指向编程PDF基础的工作链接。只需在文本中搜索"exception"。
如果使用初始异常引发新的异常,也将保留初始堆栈跟踪。
1 2 3 4 5 |
实际上,在某些情况下,
1 2 3 4 5 6 7 8 9 10 11 12 | try { int i = 0; int j = 12 / i; // Line 47 int k = j + 1; } catch { // do something // ... throw; // Line 54 } |
stacktrace将指示第54行引发了异常,尽管它是在第47行引发的。
1 2 3 | Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero. at Program.WithThrowIncomplete() in Program.cs:line 54 at Program.Main(String[] args) in Program.cs:line 106 |
在如上所述的情况下,有两个选项可以预设原始StackTrace:
调用exception.internalPreserveStackTrace
由于它是私有方法,因此必须使用反射调用它:
1 2 3 4 5 6 | private static void PreserveStackTrace(Exception exception) { MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); preserveStackTrace.Invoke(exception, null); } |
我有一个缺点,那就是依赖一个私有方法来保存stacktrace信息。它可以在.NET Framework的未来版本中更改。上面的代码示例和下面的解决方案是从FabriceMarguerie的日志中提取的。
调用exception.setObjectData
下面的技术是由Anton Tykhyy提出的,作为C中的答案,我如何在不丢失堆栈跟踪问题的情况下重新发送InnerException。
1 2 3 4 5 6 7 8 9 10 11 12 | static void PreserveStackTrace (Exception e) { var ctx = new StreamingContext (StreamingContextStates.CrossAppDomain) ; var mgr = new ObjectManager (null, ctx) ; var si = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; e.GetObjectData (si, ctx) ; mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData mgr.DoFixups () ; // ObjectManager calls SetObjectData // voila, e is unmodified save for _remoteStackTraceString } |
虽然它的优点是只依赖于公共方法,但它还依赖于以下异常构造函数(第三方开发的一些异常没有实现):
1 2 3 4 | protected Exception( SerializationInfo info, StreamingContext context ) |
在我的情况下,我必须选择第一种方法,因为我使用的第三方库引发的异常没有实现这个构造函数。
当您
经验法则是避免捕捉和投掷基本的
但在现实世界中,捕获和记录基本异常也是一个好的实践,但不要忘记走完整的路去获取它可能拥有的任何
没有人解释过
重新引发捕获的异常的完整方法是使用
下面是测试这一点的必要案例:
1。
1 2 3 4 5 6 7 8 9 10 11 |
2。
1 2 3 4 5 6 7 8 9 10 11 12 | void CallingMethod() { try { throw new Exception("TEST" ); } catch( Exception ex ) { ExceptionDispatchInfo.Capture( ex ).Throw(); throw; // So the compiler doesn't complain about methods which don't either return or throw. } } |
三。
1 2 3 4 5 6 7 8 9 10 11 |
4。
1 2 3 4 5 6 7 8 9 10 11 |
案例1和案例2将给出一个堆栈跟踪,其中
但是,案例3将给出一个堆栈跟踪,其中
案例4与案例2类似,因为保留了原始异常的行号,但由于它更改了原始异常的类型,因此不是真正的重新引发。
一些人实际上错过了一个非常重要的点——"throw"和"throw ex"可能会做同样的事情,但他们没有给你一个关键的信息,这就是发生异常的那条线。
请考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | static void Main(string[] args) { try { TestMe(); } catch (Exception ex) { string ss = ex.ToString(); } } static void TestMe() { try { //here's some code that will generate an exception - line #17 } catch (Exception ex) { //throw new ApplicationException(ex.ToString()); throw ex; // line# 22 } } |
当您执行"throw"或"throw ex"操作时,您会得到堆栈跟踪,但第行将是22,因此您无法确定到底是哪一行引发了异常(除非您在try块中只有一行或几行代码)。要获得异常中预期的第17行,您必须使用原始异常堆栈跟踪抛出一个新的异常。
应始终使用"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 | try { // Dangerous code } finally { // clean up, or do nothing } |
任何抛出的异常都将冒泡到处理它们的下一个级别。
我肯定会用:
1 2 3 4 5 6 7 8 9 10 | try { //some code } catch { //you should totally do something here, but feel free to rethrow //if you need to send the exception up the stack. throw; } |
这将保留您的堆栈。
仅供参考,我刚刚测试了这个,并且'throw;'报告的堆栈跟踪不是完全正确的堆栈跟踪。例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | private void foo() { try { bar(3); bar(2); bar(1); bar(0); } catch(DivideByZeroException) { //log message and rethrow... throw; } } private void bar(int b) { int a = 1; int c = a/b; // Generate divide by zero exception. } |
堆栈跟踪正确地指向异常的起源(报告的行号),但为foo()报告的行号是throw;语句的行,因此您无法判断对bar()的调用中哪一个导致了异常。