Select case V. If statements

Depends on the situation - a select case is often cleaner and more maintainable when you have one condition but several possibilities, if .. else if .. else allows you to have different and more complex decision making logic for each if / else if.
 
For simple matching, is switch-case more efficient than a long list of if-elses? I think I read somewhere that it is compiled internally to a hashtable, or something similar.
 
Select Case compilation

It depends on the nature of the Select Case (or switch in C#) block. Those which have sequential numeric conditions compile to a special switch instruction containing the list of branch locations. Those which have non-sequential or non-numeric conditions tend to compile to a series of conditional statements, as if they were each if/else conditions. For example:

C#:
string s = "hello world";
int i;

switch (s[1]) { //What is the second letter?
    case 'h': i = 1; break;
    case 'e': i = 2; break;
    case 'l': i = 3; break;
    case 'o': i = 4; break;
    default: i = 5; break;
}

switch (i) {
    case 1: s = "h"; break;
    case 2: s = "e"; break;
    case 3: s = "l"; break;
    case 4: s = "o"; break;
    default: s = string.Empty; break;
}

The first switch block, being non-sequential, compiles into a series of conditional tests:

Code:
  IL_0008:  callvirt   instance char [mscorlib]System.String::get_Chars(int32)
  IL_000d:  stloc.2
  IL_000e:  ldloc.2
  IL_000f:  ldc.i4.s   104
  IL_0011:  bgt.s      IL_001f    ;greater than 'h'? (special conditional)
  IL_0013:  ldloc.2
  IL_0014:  ldc.i4.s   101
  IL_0016:  beq.s      IL_002f    ;conditional branch (e)
  IL_0018:  ldloc.2
  IL_0019:  ldc.i4.s   104
  IL_001b:  beq.s      IL_002b    ;conditional branch (h)
  IL_001d:  br.s       IL_003b
  IL_001f:  ldloc.2
  IL_0020:  ldc.i4.s   108
  IL_0022:  beq.s      IL_0033    ;conditional branch (l)
  IL_0024:  ldloc.2
  IL_0025:  ldc.i4.s   111
  IL_0027:  beq.s      IL_0037    ;conditional branch (o)
  IL_0029:  br.s       IL_003b
  IL_002b:  ldc.i4.1              ;case 'h'
  IL_002c:  stloc.1
  IL_002d:  br.s       IL_003d
  IL_002f:  ldc.i4.2              ;case 'e'
  IL_0030:  stloc.1
  IL_0031:  br.s       IL_003d
  IL_0033:  ldc.i4.3              ;case 'l'
  IL_0034:  stloc.1
  IL_0035:  br.s       IL_003d
  IL_0037:  ldc.i4.4              ;case 'o'
  IL_0038:  stloc.1
  IL_0039:  br.s       IL_003d
  IL_003b:  ldc.i4.5              ;default
  IL_003c:  stloc.1

The second switch block, having sequential conditions, compiles to a switch instruction:

Code:
  IL_0042:  switch     ( 
                        IL_0059,
                        IL_0061,
                        IL_0069,
                        IL_0071)  ;single branching instruction
  IL_0057:  br.s       IL_0079
  IL_0059:  ldstr      "h"        ;case 1
  IL_005e:  stloc.0
  IL_005f:  br.s       IL_007f
  IL_0061:  ldstr      "e"        ;case 2
  IL_0066:  stloc.0
  IL_0067:  br.s       IL_007f
  IL_0069:  ldstr      "l"        ;case 3
  IL_006e:  stloc.0
  IL_006f:  br.s       IL_007f
  IL_0071:  ldstr      "o"        ;case 4
  IL_0076:  stloc.0
  IL_0077:  br.s       IL_007f
  IL_0079:  ldsfld     string [mscorlib]System.String::Empty ;default
  IL_007e:  stloc.0

For a series of string comparisons, the first style is used, with a series of calls to op_Equality and then branching from there:

Code:
  IL_008b:  ldstr      "foo"
  IL_0090:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_0095:  brtrue.s   IL_00c3
  IL_0097:  ldloc.s    CS$0$0002
  IL_0099:  ldstr      "bar"
  IL_009e:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_00a3:  brtrue.s   IL_00ca
  IL_00a5:  ldloc.s    CS$0$0002
  IL_00a7:  ldstr      "pin"
  IL_00ac:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_00b1:  brtrue.s   IL_00d1
  IL_00b3:  ldloc.s    CS$0$0002
  IL_00b5:  ldstr      "pop"
  IL_00ba:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_00bf:  brtrue.s   IL_00d8

Good luck :)
 
Last edited:
What about a case like this:
Code:
switch (integer)
{
    case 1:
        break;
    case 2:
        break;
    case 3:
        break;
    case 4:
        break;
    case 9:
        break;
    case 10:
        break;
    case 11:
        break;
}
 
Jump targets

In that case the switch instruction is used with 11 jump targets (1 to 11), and the jumps for values 5, 6, 7, and 8 all branch to the end of the code block.

If, however, the jump conditions are 1, 2, 3, 4 and 999, 1000, 1001 (large gap in values) then the compiler generates 2 switch blocks (1, 2, 3, 4 and 999, 1000, 1001). The values 999, 1000 and 1001 fail the first switch, it then subtracts 999 from the number and enters the second switch block, where they may succeed. Switch blocks are 0-based.
 
Last edited:
I believe the compiler has a little flexibility. If the values are mostly sequential and/or all within a small range the compiler will use a "calculated goto," the list of branches mentioned by MrPaul. I was under the impression that string-based select cases used a hashtable. The best way to find out, though, would be to compile code and disassemble it with reflector.
 
Hashtables

I believe the compiler has a little flexibility.

Exactly. Personally I would suggest worrying less about the efficiency and simply choosing constructs which make the most sense and are easiest to read.

I was under the impression that string-based select cases used a hashtable. The best way to find out, though, would be to compile code and disassemble it with reflector.

I just did a quick test and it appears that if there are fewer than 6 case labels, then the compiler generates a series of conditional tests. With 6 or more case labels, a Dictionary<string,int32> is used to associate case values with their 'index' in the case block (0-based). This is then used in a switch instruction which does the branching. However, this is unlikely to be a set-in-stone rule so you should not rely on it. Compiler code optimizations can be quite advanced.

Code:
  IL_00ea:  volatile.
  IL_00ec:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{F321BCDB-EE97-4BEC-9C3A-65BCE7603F07}'::'$$method0x6000005-1'
  IL_00f1:  ldloc.s    CS$0$0002
  IL_00f3:  ldloca.s   CS$0$0003
  IL_00f5:  call       instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::TryGetValue(!0,
                                                                                                                        !1&)
  IL_00fa:  brfalse.s  IL_014d    ;no match - go to default
  IL_00fc:  ldloc.s    CS$0$0003
  IL_00fe:  switch     ( 
                        IL_011d,
                        IL_0125,
                        IL_012d,
                        IL_0135,
                        IL_013d,
                        IL_0145)

:cool:
 
I generally wouldn't worry about performance between if and case, unless your app really needs that level of tweaking. For me, I rarely need a switch unless I've got some kind of factory method that's making decisions.

Two other thoughts:
1. Switch statements might be a bad smell (from refactoring) if you have duplicated switches in code.
2. See Code Complete 2's Checklist

Since CC2 doesn't provide a clear "which to use and when" guide, I'm guessing the choice to if or case falls into one of two categories: first, decide if there's a reason to NOT use one or the other (the checklist should help) and then just go by personal/professional opinion.

-ner
 
I was under the impression that it did work by table lookup, not because I disassembled the compiled code, but because of how the debugger worked. In a switch, it jumps to the correct case, but by if-else-if-else, if tested each one. Obviously that may not mean anything when it comes down to execution speed, it does make it a little less grating when stepping through code though.
 
Back
Top