Reputation: 6920
How does the catch
keyword determine the type of exception that was thrown? What process occurs to select which catch block to execute?
try
{
int[] myArray = new int[0];
myArray[1] = 0;
}
catch (IndexOutOfRangeException ex) { } // how does the CLR know to enter here?
catch (InvalidCastException ex) { }
Via ILdasm
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 28 (0x1c)
.maxstack 3
.locals init (int32[] V_0,
class [mscorlib]System.IndexOutOfRangeException V_1,
class [mscorlib]System.InvalidCastException V_2)
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: ldc.i4.0
IL_0003: newarr [mscorlib]System.Int32
IL_0008: stloc.0
IL_0009: ldloc.0
IL_000a: ldc.i4.1
IL_000b: ldc.i4.0
IL_000c: stelem.i4
IL_000d: nop
IL_000e: leave.s IL_001a
} // end .try
catch [mscorlib]System.IndexOutOfRangeException
{
IL_0010: stloc.1
IL_0011: nop
IL_0012: nop
IL_0013: leave.s IL_001a
} // end handler
catch [mscorlib]System.InvalidCastException
{
IL_0015: stloc.2
IL_0016: nop
IL_0017: nop
IL_0018: leave.s IL_001a
} // end handler
IL_001a: nop
IL_001b: ret
} // end of method Program::Main
But it is still not clear what the catch
keyword does in order to determine the type of exception that was thrown.
Upvotes: 1
Views: 322
Reputation: 941635
Just a short answer, a real one requires a book. Exception handling in .NET is ridiculously complicated, involving many moving parts. Including the native support for exceptions in Windows (SEH, Structured Exception Handling), one of the largest and most complicated chunks of code in the CLR (excep.cpp, 232KB of code), metadata in the assembly (which is why you don't see it taking any IL addresses) and the jitter.
Exceptions are raised by the throw keyword, it triggers the RaiseException() Windows api function at runtime. Windows goes looking for code that's willing to handle the exception by running exception filters, registered by RtlAddFunctionTable(). Which are implemented in the CLR. It in turns uses metadata that's generated by the jitter, tables of data that are built at just-in-time compile time when the jitter converts IL to machine code. The jitter uses the metadata info that was added to the assembly metadata by directives like .try and catch, what you see in the disassembly. The table data has info about what range of code represents the catch clause for a particular exception type. Allowing the CLR to pick a location where execution should resume and tell Windows to move on with handling the exception.
Knowing where it started and knowing where to stop, Windows now starts unwinding stack frames, calling finally blocks where necessary. Next, the instruction pointer it set at the first machine code instruction of the catch block.
Lots and lots of nasty little details beyond this. Like the semantics of ThreadAbortException, the vb.net Catch When keyword, the complication of finally blocks raising exceptions, dealing with try blocks scope when SEH is purely stack frame based, the notion of uncatchable exceptions when threads need to be aborted. The stuff that takes a book but nobody will ever write one because the reader will instantly fall asleep.
Exception handling in .NET is an iceberg filled with lead. 99% is under water, one of the most successful abstractions in the CLR. Other than programmers using the catch keyword too often, that however doesn't have anything to do with the way it was implemented.
Upvotes: 12
Reputation: 7692
Disclaimer: I hadn't read the CLR specs and I know that exceptions are far more complex than "normal" code. However, this is a very simple approach that I believe that would work.
IndexOutOfBounds
or HttpException
rather than the base Exception
try
block, the runtime will have a list of expected type of exceptions (that is, one for each catch
block). Let's say that it would be List<Type> TheseExceptionsMayOccur
.
foreach(Type ExceptionType in TheseExceptionMayOccur)
{
if(e is ExceptionType )
//run the respective catch block
}
Upvotes: 2