Saturday, August 9, 2008

How the CLR Compiler Handles Try/Catch Statements in C#

Recently I received a question on the Microsoft Forums regarding the way that exception handling works in .NET. Specifically, the question was about how the C# compiler interprets different methods of catching and rethrowing errors.

There are three ways of using the "catch" keyword in C#:

1. No error type specified: "catch"
2. Error type specified but not stored to a variable: "catch (Exception)"
3. Error type specified and stored to a variable: "catch (Exception e)"

I've implemented all three of these styles in the code below, and have compiled all three of them to see how the compiler will implement this:

using System;

namespace ErrorHandling
{
class ErrorHandlingExamples
{
public void Run()
{
int i = 0;

try
{
int a = 25 / i;
}
catch
{
throw;
}

try
{
int b = 25 / i;
}
catch (Exception)
{
throw;
}

try
{
int c = 25 / i;
}
catch (Exception e)
{
throw e;
}
}
}
}
Here's the IL Generated code:
.method public hidebysig instance void  Run() cil managed
{
// Code size 33 (0x21)
.maxstack 2
.locals init ([0] int32 i,
[1] class [mscorlib]System.Exception e)
IL_0000: ldc.i4.0
IL_0001: stloc.0
.try
{
IL_0002: ldc.i4.s 25
IL_0004: ldloc.0
IL_0005: div
IL_0006: pop
IL_0007: leave.s IL_000c
} // end .try
catch [mscorlib]System.Object
{
IL_0009: pop
IL_000a: rethrow
} // end handler
.try
{
IL_000c: ldc.i4.s 25
IL_000e: ldloc.0
IL_000f: div
IL_0010: pop
IL_0011: leave.s IL_0016
} // end .try
catch [mscorlib]System.Exception
{
IL_0013: pop
IL_0014: rethrow
} // end handler
.try
{
IL_0016: ldc.i4.s 25
IL_0018: ldloc.0
IL_0019: div
IL_001a: pop
IL_001b: leave.s IL_0020
} // end .try
catch [mscorlib]System.Exception
{
IL_001d: stloc.1
IL_001e: ldloc.1
IL_001f: throw
} // end handler
IL_0020: ret
} // end of method ErrorHandlingExamples::Run

So here's our result:

In the first situation, since no exception type is specified, the compiler generates a catch to catch "System.Object". The value of System.Object is placed on the top of the method stack, but is popped off immediately (IL_0009), and the "rethrow" IL command is called, which simply re-throws the exception generated within the protected block of code (the "try" block).

In the second situation, since an exception type is specified, the compiler generates a catch block that catches only types that inherit from System.Exception (which theoretically is every exception generated in .NET). Again, it pops the exception off of the top of the method stack (IL_0013), and re-throws the exception generated in the protected block of code using the "rethrow"command.

In the third situation, an exception type is specified, and is directed to be stored in a local variable. Here, you'll notice no pop IL command is issued, but rather the value on the top of the method stack is stored in memory at location 1 (IL_001d). Then it is loaded again (IL_001e) and the value that is placed on the top of the method stack is thrown by calling the "throw" command, which does take a specific exception to throw.

While the IL command "rethrow" throws the exception generated in the try block, the "throw" command throws the exception placed on the top of the stack, thus allowing you to create your own exception if you desire.