代码之家  ›  专栏  ›  技术社区  ›  Seibar

捕获和重新引发.NET异常的最佳实践

  •  266
  • Seibar  · 技术社区  · 17 年前

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

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

    Vs:

    try
    {
        //some code
    }
    catch
    {
        throw;
    }
    
    11 回复  |  直到 7 年前
        1
  •  253
  •   LW001 Rohan Muhammad    8 年前

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

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

    throw ex; 基本上类似于从该点抛出异常,因此堆栈跟踪只会转到您发出 抛出; 语句。

    Mike 也是正确的,假设异常允许您传递异常(建议这样做)。

    Karl Seguin 有一个 great write up on exception handling 在他的 foundations of programming e-book 还有,这本书读得很好。

    编辑:工作链接到 Foundations of Programming PDF。只需在文本中搜索“exception”。

        2
  •  93
  •   Mike    17 年前

    如果使用初始异常引发新的异常,也将保留初始堆栈跟踪。

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

    实际上,有些情况下 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 weblog .

    调用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
    )
    

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

        4
  •  19
  •   Forgotten Semicolon adrianm    17 年前

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

        5
  •  13
  •   swilliams    17 年前

    经验法则是避免抓住和扔掉 Exception 对象。这迫使您对异常更聪明一点;换句话说,您应该对 SqlException 这样您的处理代码就不会对 NullReferenceException .

    但在现实世界中 测井 基本异常也是一个很好的实践,但是不要忘记走整条路来获得任何 InnerExceptions 可能是这样。

        6
  •  8
  •   notlkk    14 年前

    一些人实际上错过了一个非常重要的点——“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块中只有一行或几行代码)。要获得异常中预期的第17行,您必须使用原始异常堆栈跟踪抛出一个新的异常。

        7
  •  8
  •   Community Mohan Dere    12 年前

    应始终使用“throw;”重新引发.NET中的异常,

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

    基本上,msil(cil)有两条指令——“throw”和“rethrow”:

    • C的“throw ex”;编译成MSIL的“throw”
    • C的“throw;”-进入msil“rethrow”!

    基本上,我可以看到为什么“throw-ex”会覆盖堆栈跟踪。

        8
  •  8
  •   loki    9 年前

    没有人能解释 ExceptionDispatchInfo.Capture( ex ).Throw() 平原 throw ,就在这里。然而,一些人注意到了 .

    重新引发捕获的异常的完整方法是使用 exceptionDispatchInfo.capture(例如.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.
        }
    }
    

    三。

    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将为您提供一个堆栈跟踪,其中 调用方法 方法是 打电话。这意味着如果 引发新异常(“测试”) 行被其他操作包围,您不知道异常实际上是在哪个行号处抛出的。

    案例4与案例2类似,因为保留了原始异常的行号,但由于它更改了原始异常的类型,因此不是真正的重新引发。

        9
  •  3
  •   Erick B    17 年前

    您还可以使用:

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

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

        10
  •  3
  •   Kevin Griffin    11 年前

    我肯定会用:

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

    这将保留您的堆栈。

        11
  •  0
  •   redcalx    15 年前

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

    推荐文章