捕获异常并重新抛出异常时需要考虑哪些最佳实践?我想确保保留Exception
对象InnerException
和堆栈跟踪.以下代码块在处理此方式时是否存在差异?
try { //some code } catch (Exception ex) { throw ex; }
VS:
try { //some code } catch { throw; }
Darren Kopp.. 260
保留堆栈跟踪的方法是通过使用throw;
它也是有效的
try { // something that bombs here } catch (Exception ex) { throw; }
throw ex;
基本上就像从那一点抛出一个异常,所以堆栈跟踪只会到你发出throw ex;
语句的地方.
Mike也是正确的,假设异常允许您传递异常(建议使用).
Karl Seguin在编写电子书的基础上对异常处理有很好的写作,这是一本很好的读物.
编辑:编程基础的工作链接pdf.只需在文本中搜索"例外"即可.
保留堆栈跟踪的方法是通过使用throw;
它也是有效的
try { // something that bombs here } catch (Exception ex) { throw; }
throw ex;
基本上就像从那一点抛出一个异常,所以堆栈跟踪只会到你发出throw ex;
语句的地方.
Mike也是正确的,假设异常允许您传递异常(建议使用).
Karl Seguin在编写电子书的基础上对异常处理有很好的写作,这是一本很好的读物.
编辑:编程基础的工作链接pdf.只需在文本中搜索"例外"即可.
如果您使用初始异常抛出一个新异常,那么您也将保留初始堆栈跟踪.
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行引发了异常,尽管它是在第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
因为它是一个私有方法,所以必须使用反射来调用它:
private static void PreserveStackTrace(Exception exception) { MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); preserveStackTrace.Invoke(exception, null); }
我的缺点是依赖私有方法来保存StackTrace信息.它可以在.NET Framework的未来版本中更改.上面的代码示例和下面提出的解决方案是从Fabrice MARGUERIE博客中提取的.
调用Exception.SetObjectData
Anton Tykhyy建议将下面的技术作为In 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 }
虽然它具有依赖公共方法的优点,但它还依赖于以下异常构造函数(第三方开发的一些例外不实现):
protected Exception( SerializationInfo info, StreamingContext context )
在我的情况下,我不得不选择第一种方法,因为我使用的第三方库引发的异常没有实现这个构造函数.
当你throw ex
,你实际上抛出一个新的异常,并错过了原始的堆栈跟踪信息. throw
是首选的方法.
经验法则是避免捕获和抛出基本Exception
对象.这迫使你对异常更聪明一点; 换句话说,你应该有一个明确的catch,SqlException
以便你的处理代码不会出错NullReferenceException
.
在现实世界中,捕获和记录基本异常也是一种很好的做法,但不要忘记走完整个事情以获得InnerExceptions
它可能具有的任何东西.
你应该总是使用"扔"; 重新抛出.NET中的异常,
请参阅此处, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
基本上MSIL(CIL)有两条指令 - "throw"和"rethrow":
C#的"扔前"; 被编译成MSIL的"扔"
C#的"扔"; - 进入MSIL"重新抛出"!
基本上我可以看到"throw ex"覆盖堆栈跟踪的原因.
没有人解释ExceptionDispatchInfo.Capture( ex ).Throw()
和平原之间的区别throw
,所以在这里.但是,有些人已经注意到了这个问题throw
.
重新抛出捕获的异常的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw()
(仅可从.Net 4.5获得).
下面是测试这个的必要案例:
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" )
行被其他操作包围,您不知道实际抛出异常的行号.
情况4与情况2类似,因为原始异常的行号被保留,但不是真正的重新抛出,因为它改变了原始异常的类型.
一些人实际上错过了一个非常重要的观点 - 'throw'和'throw ex'可能会做同样的事情,但他们没有给你一个关键的信息,这是异常发生的线.
请考虑以下代码:
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,所以你无法确定究竟是哪一行抛出了异常(除非你只有1或几个) try块中的代码行).要在异常中获得预期的第17行,您必须使用原始异常堆栈跟踪抛出新的异常.