捕获和重新抛出.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。