Best practices for catching and re-throwing .NET exceptions

前端 未结 11 1483
野的像风
野的像风 2020-11-22 07:15

What are the best practices to consider when catching exceptions and re-throwing them? I want to make sure that the Exception object\'s InnerException

相关标签:
11条回答
  • 2020-11-22 08:06

    I would definitely use:

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

    That will preserve your stack.

    0 讨论(0)
  • 2020-11-22 08:08

    Nobody has explained the difference between ExceptionDispatchInfo.Capture( ex ).Throw() and a plain throw, so here it is. However, some people have noticed the problem with throw.

    The complete way to rethrow a caught exception is to use ExceptionDispatchInfo.Capture( ex ).Throw() (only available from .Net 4.5).

    Below there are the cases necessary to test this:

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

    Case 1 and case 2 will give you a stack trace where the source code line number for the CallingMethod method is the line number of the throw new Exception( "TEST" ) line.

    However, case 3 will give you a stack trace where the source code line number for the CallingMethod method is the line number of the throw call. This means that if the throw new Exception( "TEST" ) line is surrounded by other operations, you have no idea at which line number the exception was actually thrown.

    Case 4 is similar with case 2 because the line number of the original exception is preserved, but is not a real rethrow because it changes the type of the original exception.

    0 讨论(0)
  • 2020-11-22 08:11

    You may also use:

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

    And any exceptions thrown will bubble up to the next level that handles them.

    0 讨论(0)
  • 2020-11-22 08:12

    Actually, there are some situations which the throw statment will not preserve the StackTrace information. For example, in the code below:

    try
    {
      int i = 0;
      int j = 12 / i; // Line 47
      int k = j + 1;
    }
    catch
    {
      // do something
      // ...
      throw; // Line 54
    }
    

    The StackTrace will indicate that line 54 raised the exception, although it was raised at line 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
    

    In situations like the one described above, there are two options to preseve the original StackTrace:

    Calling the Exception.InternalPreserveStackTrace

    As it is a private method, it has to be invoked by using reflection:

    private static void PreserveStackTrace(Exception exception)
    {
      MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
        BindingFlags.Instance | BindingFlags.NonPublic);
      preserveStackTrace.Invoke(exception, null);
    }
    

    I has a disadvantage of relying on a private method to preserve the StackTrace information. It can be changed in future versions of .NET Framework. The code example above and proposed solution below was extracted from Fabrice MARGUERIE weblog.

    Calling Exception.SetObjectData

    The technique below was suggested by Anton Tykhyy as answer to In C#, how can I rethrow InnerException without losing 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 
    } 
    

    Although, it has the advantage of relying in public methods only it also depends on the following exception constructor (which some exceptions developed by 3rd parties do not implement):

    protected Exception(
        SerializationInfo info,
        StreamingContext context
    )
    

    In my situation, I had to choose the first approach, because the exceptions raised by a 3rd-party library I was using didn't implement this constructor.

    0 讨论(0)
  • 2020-11-22 08:15

    A few people actually missed a very important point - 'throw' and 'throw ex' may do the same thing but they don't give you a crucial piece of imformation which is the line where the exception happened.

    Consider the following code:

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

    When you do either a 'throw' or 'throw ex' you get the stack trace but the line# is going to be #22 so you can't figure out which line exactly was throwing the exception (unless you have only 1 or few lines of code in the try block). To get the expected line #17 in your exception you'll have to throw a new exception with the original exception stack trace.

    0 讨论(0)
提交回复
热议问题