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

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

Vs:

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

当前回答

实际上,在某些情况下,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

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

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

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

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

其他回答

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

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

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

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

Karl Seguin在他的编程基础电子书中也写了一篇关于异常处理的很棒的文章,非常值得一读。

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

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

你也可以使用:

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

而抛出的任何异常都将上升到处理它们的下一层。

没有人解释过ExceptionDispatchInfo之间的区别。Capture(ex). 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方法的源代码行号是抛出新异常(“TEST”)行的行号。

然而,情形3将为您提供一个堆栈跟踪,其中CallingMethod方法的源代码行号是抛出调用的行号。这意味着如果抛出新的异常(“TEST”)行被其他操作包围,则您不知道异常实际上是在第几行抛出的。

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

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

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