@evildictaitor: Don't underestimate the C# compiler wrt the switch statement. A few examples:

  • Switches with a low number of cases can become if statements.
  • Switches with reasonably contigious ranges can be turned into the switch instruction. Holes can be patched with a jump target to the default case.
  • Switches with a non-zero case can get rebased using an add or sub instruction in order to use the switch instruction again.
  • Switches with sparse case values can be turned into nested/hierarchical switch instructions, using any of the above techniques applied.
  • Switches on string cases can become if statements, but if there are more than a treshold number (currently 6 IIRC), a Dictionary<string, int> is lazily constructed, using the int values for switch instruction use.

Just a little example:

[MethodImpl(MethodImplOptions.NoInlining)]
static void Bar(int i, out int j)
{
    switch (i)
    {
        case 17:
            j = 1;
            break;
        case 19:
            j = 2;
            break;
        case 20:
            j = 3;
            break;
        case 25:
            j = 4;
            break;
        default:
            j = 0;
            break;
    }
}

Code above got compiled without /o+, so you'll see redundant nop instruction and excessive st/ld pairs in the IL. Using SOS, we dump the IL and the generated assembler:

0:004> !name2ee switch.exe Program.Bar
Module:      000007ff000330d8
Assembly:    switch.exe
Token:       0000000006000002
MethodDesc:  000007ff000340e0
Name:        Program.Bar(Int32, Int32 ByRef)
JITTED Code Address: 000007ff001501d0
0:004> !dumpil 000007ff000340e0
ilAddr = 000000000137207c
IL_0000: nop 
IL_0001: ldarg.0 
IL_0002: stloc.0 
IL_0003: ldloc.0 
IL_0004: ldc.i4.s 17
IL_0006: sub 
IL_0007: switch (IL_0023, IL_0037, IL_0028, IL_002d)
IL_001c: ldloc.0 
IL_001d: ldc.i4.s 25
IL_001f: beq.s IL_0032
IL_0021: br.s IL_0037
IL_0023: ldarg.1 
IL_0024: ldc.i4.1 
IL_0025: stind.i4 
IL_0026: br.s IL_003c
IL_0028: ldarg.1 
IL_0029: ldc.i4.2 
IL_002a: stind.i4 
IL_002b: br.s IL_003c
IL_002d: ldarg.1 
IL_002e: ldc.i4.3 
IL_002f: stind.i4 
IL_0030: br.s IL_003c
IL_0032: ldarg.1 
IL_0033: ldc.i4.4 
IL_0034: stind.i4 
IL_0035: br.s IL_003c
IL_0037: ldarg.1 
IL_0038: ldc.i4.0 
IL_0039: stind.i4 
IL_003a: br.s IL_003c
IL_003c: ret 

Notice the sub instruction to rebase the switch statement and the insertion of case 18 equals default case by a jump to IL_0037 in the switch instruction's table. Case 25 which is further away from the switch range encountered here is handled by fall-through after the switch instruction and a jump to IL_0032. If you'd have a cluster of cases close to 25 again, you'd see another switch instruction being born.

I'll leave it as a good SOS exercise to the reader to perform a !U on the JITTED code address for different platform architectures and unravvle the assember generated...

0:004> !u 000007ff001501d0
Normal JIT generated code
Program.Bar(Int32, Int32 ByRef)
Begin 000007ff001501d0, size 62
...

Also don't forget order of cases in a switch statement doesn't matter, so the techniques described above can be applied even if cases don't appear in a sorted lexical order.