Ian R. O'Brien
Ian R. O'Brien

Reputation: 6920

How does the catch keyword determine the type of exception that was thrown?

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

Answers (2)

Hans Passant
Hans Passant

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

Andre Calil
Andre Calil

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.

  1. When an exception is thrown, the code that's generating it will use a specific type, such as IndexOutOfBounds or HttpException rather than the base Exception
  2. When an exception is caught inside a 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
  3. Finally, it would be something like

.

foreach(Type ExceptionType in TheseExceptionMayOccur)
{
    if(e is ExceptionType )
        //run the respective catch block
}

Upvotes: 2

Related Questions