捕获和重新抛出.NETexception的最佳实践

捕捉exception并重新抛出exception时需要考虑哪些最佳实践? 我想确保Exception对象的InnerException和堆栈跟踪被保留。 下面的代码块在处理这个方面有什么区别吗?

 try { //some code } catch (Exception ex) { throw ex; } 

VS:

 try { //some code } catch { throw; } 

保存堆栈跟踪的方法是通过使用throw; 这也是有效的

 try { // something that bombs here } catch (Exception ex) { throw; } 

throw ex; 基本上就像是从这一点抛出一个exception,所以堆栈跟踪只会发送到你发出throw ex; 声明。

迈克也是正确的,假设exception允许你传递一个exception(推荐)。

卡尔·塞甘(Karl Seguin)在编程电子书的基础 上写了很多关于exception处理的文章 ,这是一个很棒的阅读。

编辑:工作链接到编程基础 pdf。 只要search“exception”的文字。

如果抛出一个新的exception,并且初始的exception,你也会保存初始的堆栈跟踪。

 try{ } catch(Exception ex){ throw new MoreDescriptiveException("here is what was happening", ex); } 

实际上,在某些情况下, throw语句不会保留StackTrace信息。 例如,在下面的代码中:

 try { int i = 0; int j = 12 / i; // Line 47 int k = j + 1; } catch { // do something // ... throw; // Line 54 } 

StackTrace将显示第54行提出exception,虽然它是在第47行提出的。

 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

由于这是一个私有方法,它必须使用reflection来调用:

 private static void PreserveStackTrace(Exception exception) { MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); preserveStackTrace.Invoke(exception, null); } 

我有一个缺点,依靠私有方法来保存StackTrace信息。 它可以在.NET Framework的未来版本中进行更改。 上面的代码示例和下面提出的解决scheme是从Fabrice MARGUERIE博客中提取的。

调用Exception.SetObjectData

下面的技术是由Anton Tykhyy作为C#中的答案提出的,我如何在不丢失堆栈跟踪问题的情况下重新抛出InnerException 。

 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 } 

虽然它依赖于公共方法的优点,但也依赖于下面的exception构造函数(第三方开发的一些exception没有实现):

 protected Exception( SerializationInfo info, StreamingContext context ) 

在我的情况下,我不得不select第一种方法,因为我使用的第三方库引发的exception没有实现这个构造函数。

当你throw ex ,你基本上抛出一个新的exception,并会错过了原始的堆栈跟踪信息。 throw是首选的方法。

经验法则是避免捕捉和抛出基本的Exception对象。 这迫使你在例外情况下变得更聪明一些; 换句话说,你应该有一个显式的SqlException这样你的处理代码就不会在NullReferenceException出错。

但是在现实世界中,捕获和logging基本exception也是一个好习惯,但是不要忘了整个过程来获取它可能具有的任何InnerExceptions

有几个人实际上错过了一个非常重要的观点 – “抛出”和“抛出”可能会做同样的事情,但是他们并没有给出一个关键信息,即exception发生的路线。

考虑下面的代码:

 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,所以你不能确定哪一行正好抛出exception(除非你只有一个或几个try块中的代码行)。 为了在你的exception中得到预期的第17行,你将不得不抛出一个新的exception与原始exception堆栈跟踪。

你应该总是使用“扔”; 在.NET中重新抛出exception,

请参阅http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

基本上MSIL(CIL)有两条指令 – “抛出”和“重新抛出”:

  • C#的“扔掉”; 被编译成MSIL的“throw”
  • C#的“抛出” – 进入MSIL“重新抛出”!

基本上我可以看到“抛出”覆盖栈跟踪的原因。

没有人解释了ExceptionDispatchInfo.Capture( ex ).Throw()和普通的throw之间的区别,所以在这里。 但是,有些人已经注意到throw的问题。

重新抛出捕获exception的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw() (仅在.Net 4.5中可用)。

下面是需要testing的情况:

1。

 void CallingMethod() { //try { throw new Exception( "TEST" ); } //catch { // throw; } } 

2。

 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. } } 

3。

 void CallingMethod() { try { throw new Exception( "TEST" ); } catch { throw; } } 

4。

 void CallingMethod() { try { throw new Exception( "TEST" ); } catch( Exception ex ) { throw new Exception( "RETHROW", ex ); } } 

情况1和情况2将为您提供堆栈跟踪,其中CallingMethod方法的源代码行号是throw new Exception( "TEST" )行的行号。

但是,情况3会给你一个堆栈跟踪,其中CallingMethod方法的源代码行号是throw调用的行号。 这意味着如果throw new Exception( "TEST" )行被其他操作包围,你不知道在哪个行号实际抛出exception。

情况4与情况2类似,因为原始exception的行号被保留,但不是真正的重新抛出,因为它改变了原始exception的types。

我一定会用:

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

这将保存你的堆栈。

您也可以使用:

 try { // Dangerous code } finally { // clean up, or do nothing } 

而抛出的任何exception都会冒泡到处理它们的下一个级别。

仅供参考,我只是testing了这个以及由'throw'报告的堆栈跟踪。 不是一个完全正确的堆栈跟踪。 例:

  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. } 

堆栈跟踪正确地指向exception的来源(报告的行号),但为foo()报告的行号是throw的行; 声明,因此你不能告诉bar()哪个调用导致exception。