首页 文章

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

提问于
浏览
263

捕获异常并重新抛出异常时需要考虑哪些最佳实践?我想确保保留 Exception 对象的 InnerException 和堆栈跟踪 . 以下代码块在处理此方式时是否存在差异?

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

VS:

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

11 回答

  • 251

    仅供参考我刚刚测试了这个并且'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()报告的行号是throw的行;声明,因此您无法分辨哪个调用bar()导致异常 .

  • 8

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

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

    throw ex; 基本上就像从该点抛出异常,因此堆栈跟踪只会发送到您发出 throw ex; 语句的位置 .

    Mike也是正确的,假设异常允许您传递异常(建议使用) .

    Karl Seguin在他的_1438219中也有一个great write up on exception handling,这是一个很好的阅读 .

    编辑:工作链接到Foundations of Programming pdf . 只需搜索"exception"的文本 .

  • 3

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

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

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

    Calling the 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 weblog中提取的 .

    Calling Exception.SetObjectData

    以下技术由Anton Tykhyy建议作为In C#, how can I rethrow InnerException without losing stack trace问题的答案 .

    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
    )
    

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

  • 0

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

  • 93

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

    但是在现实世界中,捕获和记录基本异常也是一种很好的做法,但是不要忘记走完整个事情来获得它可能具有的任何 InnerExceptions .

  • 18

    一些人实际上错过了一个非常重要的观点 - '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行,您必须使用原始异常堆栈跟踪抛出新的异常 .

  • 26

    你应该总是使用“扔”;重新抛出.NET中的异常,

    请参阅,http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

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

    • C#'s 1438242 gets compiled into MSIL' s "throw"

    • C#的"throw;" - 进入MSIL "rethrow"!

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

  • 7

    没有人解释 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" ) line被其他操作包围,您不知道实际抛出异常的行号 .

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

  • 8

    您也可以使用:

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

    抛出的任何异常都会升级到处理它们的下一个级别 .

  • 13

    我肯定会用:

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

    这将保留你的堆栈 .

相关问题