C# 捕获和重新抛出.NET异常的最佳实践 当捕捉异常并重新抛出它们时,最好的做法是什么?我想确保异常对象的内部异常和堆栈跟踪被保留。以下代码块在处理此问题的方式上是否存在差异 try { //some code } catch (Exception ex) { throw ex; }
Vs:C# 捕获和重新抛出.NET异常的最佳实践 当捕捉异常并重新抛出它们时,最好的做法是什么?我想确保异常对象的内部异常和堆栈跟踪被保留。以下代码块在处理此问题的方式上是否存在差异 try { //some code } catch (Exception ex) { throw ex; },c#,.net,exception-handling,rethrow,C#,.net,Exception Handling,Rethrow,Vs: 保存堆栈跟踪的方法是通过使用抛出这也是有效的 try { // something that bombs here } catch (Exception ex) { throw; } throw-ex基本上类似于从该点抛出异常,因此堆栈跟踪只会到达发出throw ex的位置语句 也正确,假设异常允许您传递异常(建议这样做) 他也有一本书,这是一本很棒的书 编辑:工作链接到pdf。只需在文本中搜索“异常”。当您抛出ex时,您实际上是在抛出一个新异常,并且将丢失原始堆栈跟踪信息
保存堆栈跟踪的方法是通过使用
抛出代码>这也是有效的
try {
// something that bombs here
} catch (Exception ex)
{
throw;
}
throw-ex代码>基本上类似于从该点抛出异常,因此堆栈跟踪只会到达发出throw ex的位置代码>语句
也正确,假设异常允许您传递异常(建议这样做)
他也有一本书,这是一本很棒的书
编辑:工作链接到pdf。只需在文本中搜索“异常”。当您抛出ex
时,您实际上是在抛出一个新异常,并且将丢失原始堆栈跟踪信息<代码>抛出是首选方法。经验法则是避免捕获和抛出基本的异常
对象。这迫使你对异常更加聪明;换句话说,您应该有一个SqlException
的显式捕获,以便您的处理代码不会对NullReferenceException
产生错误
但在现实世界中,捕获和记录基本异常也是一种很好的做法,但不要忘记了解整个过程,以获取它可能具有的任何内部异常。如果您在初始异常中抛出一个新异常,您也将保留初始堆栈跟踪
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
我肯定会使用:
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
}
任何抛出的异常都将冒泡到下一个处理它们的级别。您应该始终使用“throw;”在.NET中重新抛出异常
参考这个,,
基本上,MSIL(CIL)有两条指令——“抛出”和“重新抛出”:
- C#的“throw ex;”被编译成MSIL的“throw”
- C#的“抛出”-进入MSIL“再抛出”李>
基本上,我可以理解为什么“throw ex”会覆盖堆栈跟踪。仅供参考,我刚刚测试了这一点,以及由“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.
}
堆栈跟踪正确地指向异常的起源(报告的行号),但为foo()报告的行号是抛出的行;语句,因此您无法判断是哪个对bar()的调用导致了异常。一些人实际上错过了一个非常重要的点—“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,因此您无法确定哪一行确实引发了异常(除非try块中只有1行或几行代码)。要获得异常中的预期行#17,您必须使用原始异常堆栈跟踪抛出一个新异常。实际上,在某些情况下,throw
station不会保留堆栈跟踪信息。例如,在下面的代码中:
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:
调用异常。InternalPreserveStackTrace
由于它是一个私有方法,因此必须使用反射调用它:
private static void PreserveStackTrace(Exception exception)
{
MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
}
我的缺点是依赖私有方法来保存StackTrace信息。它可以在.NET Framework的未来版本中更改。上面的代码示例和下面建议的解决方案是从中提取的
调用异常。SetObjectData
下面的技巧是作为问题的答案而提出的
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
}
尽管它的优点是只依赖于公共方法,但它也依赖于以下异常构造函数(第三方开发的某些异常不实现):
在我的情况下,我不得不选择第一种方法,因为我使用的第三方库引发的异常没有实现此构造函数。没有人解释ExceptionDispatchInfo.Capture(ex).Throw()
和普通的Throw
之间的区别,就是这样。然而,一些人已经注意到了throw
的问题
重新显示捕获的异常的完整方法是使用ExceptionDispatchInfo.Capture(ex).Throw()
(仅可从.Net 4.5获得)
以下是测试此功能所需的案例:
一,
二,
三,
四,
案例1和案例2将为您提供一个堆栈跟踪,其中CallingMethod
方法的源代码行号是throw new Exception(“TEST”)
行的行号
但是,案例3将为您提供一个堆栈跟踪,其中CallingMethod
方法的源代码行号是throw
调用的行号。这意味着,如果抛出新异常(“TEST”)
行被其他操作包围,则您不知道异常实际抛出的行号
案例4与案例2类似,因为原始异常的行号被保留,但不是真正的重新显示,因为它更改了原始异常的类型。我不太确定这篇文章是否精彩,它建议尝试{//…}catch(exception ex){throw new exception(ex.Message+“other stuff”);}很好。问题是,除非捕获所有异常,否则您完全无法进一步处理该异常(您确定要这样做吗
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
)
void CallingMethod()
{
//try
{
throw new Exception( "TEST" );
}
//catch
{
// throw;
}
}
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.
}
}
void CallingMethod()
{
try
{
throw new Exception( "TEST" );
}
catch
{
throw;
}
}
void CallingMethod()
{
try
{
throw new Exception( "TEST" );
}
catch( Exception ex )
{
throw new Exception( "RETHROW", ex );
}
}