捕获和重新抛出.NET异常的最佳实践

在捕获异常并重新抛出异常时,应该考虑哪些最佳实践?我想确保Exception对象的InnerException和堆栈跟踪被保留。下面的代码块在处理这个问题的方式上有区别吗?

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

与:

try
{
//some code
}
catch
{
throw;
}
190747 次浏览

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

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

throw ex;基本上就像从那一点抛出一个异常,所以堆栈跟踪只会到你发出throw ex;语句的地方。

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

卡尔·赛甘·在他的编程基础电子书中也有一个关于异常处理的文章写得很好,这是一个很好的阅读。

编辑:编程基础 pdf的工作链接。在文本中搜索“例外”即可。

当你throw ex时,你实际上是在抛出一个新的异常,并且会错过原始的堆栈跟踪信息。throw是首选方法。

经验法则是避免捕获和抛出基本的Exception对象。这迫使你对异常更加聪明;换句话说,你应该对SqlException有一个显式的catch,这样你的处理代码就不会对NullReferenceException做错误的事情。

但在现实世界中,捕获和日志记录基本异常也是一个很好的实践,但不要忘记遍历整个过程以获得它可能拥有的任何InnerExceptions

如果你抛出一个带有初始异常的新异常,你也会保留初始堆栈跟踪。

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中的异常,

< p >引用这个, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx < / p >

基本上MSIL (CIL)有两个指令——“throw”和“rethrow”:

  • 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语句不会保存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:

调用异常。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博客中提取的。

调用异常。SetObjectData

下面的技巧是由安东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
}

尽管它的优点是只依赖于公共方法,但它也依赖于以下异常构造函数(由第三方开发的一些异常并不实现):

protected Exception(
SerializationInfo info,
StreamingContext context
)

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

没有人解释过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类似,因为原始异常的行号被保留,但不是真正的重抛出,因为它改变了原始异常的类型。