300

捕获异常并重新抛出它们时要考虑的最佳实践是什么?我想确保保留Exception对象InnerException和堆栈跟踪。以下代码块的处理方式有区别吗?

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

对比:

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

11 回答 11

275

保存堆栈跟踪的方法是通过使用throw;This is valid as well

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

throw ex;基本上就像从那时开始抛出异常一样,所以堆栈跟踪只会转到您发出throw ex;语句的地方。

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

Karl Seguin在他的编程基础电子书中也有一篇关于异常处理的精彩文章,这是一本很好的读物。

编辑:编程基础pdf的工作链接。只需在文本中搜索“异常”即可。

于 2008-08-22T15:13:25.197 回答
104

如果您使用初始异常抛出一个新异常,您也将保留初始堆栈跟踪。

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}
于 2008-08-22T15:18:25.577 回答
29

实际上,有些情况下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#, how can I rethrow InnerException without lost stack trace question的回答。

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 
} 

虽然,它具有仅依赖于公共方法的优点,但它还依赖于以下异常构造函数(第 3 方开发的一些异常未实现):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

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

于 2012-07-01T19:07:46.357 回答
21

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

于 2008-08-22T15:15:55.833 回答
13

经验法则是避免捕捉和投掷基本Exception对象。这迫使你对异常更聪明一点;换句话说,您应该对 a 有一个明确的捕获,SqlException以便您的处理代码不会对 a 做错什么NullReferenceException

但在现实世界中,捕获和记录基本异常也是一种很好的做法,但不要忘记遍历整个过程以获取InnerExceptions可能存在的任何异常。

于 2008-08-22T15:18:24.767 回答
12

ExceptionDispatchInfo.Capture( ex ).Throw()没有人解释和 plain之间的区别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 类似,因为保留了原始异常的行号,但不是真正的重新抛出,因为它改变了原始异常的类型。

于 2016-11-14T10:24:17.107 回答
10

您应该始终使用“投掷”;在 .NET 中重新抛出异常,

参考这个, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

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

  • C# 的“throw ex;” 被编译成 MSIL 的“投掷”
  • C# 的“抛出;” - 进入MSIL“重投”!

基本上我可以看到“throw ex”覆盖堆栈跟踪的原因。

于 2010-07-05T13:23:38.437 回答
8

有些人实际上错过了一个非常重要的点——“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”时,您会得到堆栈跟踪,但 line# 将是 #22,因此您无法确定究竟是哪一行引发了异常(除非您只有 1 个或很少try 块中的代码行)。要在异常中获得预期的第 17 行,您必须使用原始异常堆栈跟踪抛出一个新异常。

于 2012-02-08T01:09:37.800 回答
3

我肯定会使用:

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;
}

这将保留您的堆栈。

于 2008-08-22T15:19:15.840 回答
3

您还可以使用:

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

抛出的任何异常都会冒泡到处理它们的下一个级别。

于 2008-08-22T15:32:21.440 回答
0

仅供参考,我刚刚测试了这个和'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() 的哪些调用导致了异常。

于 2011-04-13T17:37:11.673 回答