GOTCHA #15 rethrow isn't consistentIn some situations, you may need to continue propagating an exception up the call stack. For instance, you may feel that you have not handled it successfully, or perhaps you just want to log the exception. In these cases, you can throw the exception again. If e is a reference to an exception object, the call that comes to mind is tHRow e. But what is the consequence of this statement? This is a good example of things getting lost in the translation to MSIL. Consider Example 2-9. Example 2-9. Behavior of a throw statementC# (rethrow) using System; namespace ThrowingException { class Test { public static void Method1() { throw new ApplicationException(); } public static void Method2() { try { Method1(); } catch(Exception ex) { // Code to log may go here. throw ex; } } public static void Method3() { try { Method1(); } catch(Exception) { // Code to log may go here. throw; } } [STAThread] static void Main(string[] args) { try { Console.WriteLine("----- Calling Method2"); Method2(); } catch(Exception ex) { Console.WriteLine(ex); } try { Console.WriteLine("----- Calling Method3"); Method3(); } catch(Exception ex) { Console.WriteLine(ex); } } } } VB.NET (rethrow) Module Test Public Sub Method1() Throw New ApplicationException End Sub Public Sub Method2() Try Method1() Catch ex As Exception 'code to log may go here Throw ex End Try End Sub Public Sub Method3() Try Method1() Catch ex As Exception 'code to log may go here Throw End Try End Sub Public Sub Main() Try Console.WriteLine("----- Calling Method2") Method2() Catch ex As Exception Console.WriteLine(ex) End Try Try Console.WriteLine("----- Calling Method3") Method3() Catch ex As Exception Console.WriteLine(ex) End Try End Sub End Module In Example 2-9, Method2() uses throw ex to propagate the exception it caught. Method3(), on the other hand, uses only tHRow. The output from the above program is shown in Figure 2-8. Note that when the exception is caught in Main() from Method2() (the one that uses throw ex), the stack trace does not indicate that Method1() was the origin of the exception. However, when the exception is caught in Main() from Method3() (the one that uses throw without the ex), the stack trace points all the way to Method1(), where the exception originated. Figure 2-8. Output from Example 2-9What is the reason for this difference? It becomes clear if you use ildasm.exe to examine the assembly for Method2() and Method3(), shown in Example 2-10 as generated from the C# code. Example 2-10. MSIL with difference between throw ex and throw.method public hidebysig static void Method2() cil managed { // Code size 11 (0xb) .maxstack 1 .locals init ([0] class [mscorlib]System.Exception ex) .try { IL_0000: call void ThrowingException.Test::Method1() IL_0005: leave.s IL_000a } // end .try catch [mscorlib]System.Exception { IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: throw } // end handler IL_000a: ret } // end of method Test::Method2 .method public hidebysig static void Method3() cil managed { // Code size 11 (0xb) .maxstack 1 .try { IL_0000: call void ThrowingException.Test::Method1() IL_0005: leave.s IL_000a } // end .try catch [mscorlib]System.Exception { IL_0007: pop IL_0008: rethrow } // end handler IL_000a: ret } // end of method Test::Method3 As you can see from the MSIL, throw ex TRanslates to a fresh throw on the call stack. However, throw by itself translates to a rethrow statement at the MSIL level. The latter results in the true rethrow of the original exception, whereas the former is treated as a new exception. So you would want to use throw instead of tHRow ex. Another option is to use the InnerException property of the Exception class to propagate the exception details. In the catch block for Method2() you can create an exception and set the exception you caught as its constructor argument, as in the following C# code segment: catch(Exception ex) { // Code to log may go here. //throw ex; throw new ApplicationException( "Exception logged", ex); } The VB.NET version is: Catch ex As Exception 'code to log may go here 'Throw ex Throw New ApplicationException("Exception logged", ex) End Try As a result of this change you will see the output shown in Figure 2-9. Figure 2-9. Output after the change to use InnerExceptionThe catch blocks in Main() call Console.WriteLine(ex). This in turn calls the ToString() method on the Exception, which prints the details of that exception instance. If the Exception contains a non-null InnerException, the ToString() method of Exception will also print the details of the inner exception. You can see this in the output between the "--->" (in the second line from the top of Figure 2-9) and "--- End of inner exception stack trace ---." IN A NUTSHELLUse throw instead of throw ex to propagate an exception. Alternately, use InnerException. SEE ALSOGotcha #26, "Details of exception may be hidden." |