Switch is just If-Else-If

Posted on April 24, 2008

0


In an internal meeting of a bunch of developers at my current company, someone made the assertion that the Switch statement in C# (or Select Case in VB.NET) is just a compiler short-cut for writing a chain of If-Then-Else statements.

What do you think? Is that statement – true or false?

Take the following C# function for example:

public string DoSomething(char c)
{
switch (Char.ToLower(c))
{
case ‘e':
case ‘g':
case ‘k':
return “something 1″;
case ‘a':
case ‘d':
case ‘h':
case ‘r':
return “something 2″;
case ‘p':
case ‘i':
case ‘l':
return “something 3″;
default:
return “something default”;
}
}

One of the ways to deal with the switch statement is for the compiler to treat the above switch statement as a sequence of If-then-else clauses, like so:

char lowerC = Char.ToLower(c);
if (lowerC == ‘e’ || lowerC == ‘g’ || lowerC == ‘k’ )
return “something 1″;
else if (lowerC == ‘a’ || lowerC == ‘d’ || lowerC == ‘h’ || lowerC == ‘r’ )
return “something 2″;
else if (lowerC == ‘p’ || lowerC == ‘i’ || lowerC == ‘l’ )
return “something 3″;
else
return “something default”;

But, if you think about it, there’s a better way…

One can do better by having a lookup table to map each character that you can possibly see, to the address of the corresponding instruction which should be executed when that character is seen. With a lookup table like that, when you see ‘p’ for example, there’s no need to compare it against ‘e’, ‘g’, ‘k’… and all the other characters in the cases that preceded it in the switch statement. As a result, your code will be faster.

There’s a “switch” statement at the IL level which implements the jump-table concept. The generated IL for DoSomething looks like the following:

.method public hidebysig instance string
DoSomething(char c) cil managed
{
// Code size 125 (0x7d)
.maxstack 2
.locals init ([0] string CS$1$0000,
[1] char CS$4$0001)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: call char [mscorlib]System.Char::ToLower(char)
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldc.i4.s 97
IL_000b: sub
IL_000c: switch (
IL_0063,
IL_0073,
IL_0073,
IL_0063,
IL_005b,
IL_0073,
IL_005b,
IL_0063,
IL_006b,
IL_0073,
IL_005b,
IL_006b,
IL_0073,
IL_0073,
IL_0073,
IL_006b,
IL_0073,
IL_0063)
IL_0059: br.s IL_0073
IL_005b: ldstr “something 1″
IL_0060: stloc.0
IL_0061: br.s IL_007b
IL_0063: ldstr “something 2″
IL_0068: stloc.0
IL_0069: br.s IL_007b
IL_006b: ldstr “something 3″
IL_0070: stloc.0
IL_0071: br.s IL_007b
IL_0073: ldstr “something default”
IL_0078: stloc.0
IL_0079: br.s IL_007b
IL_007b: ldloc.0
IL_007c: ret
}

Here’s what the compiler generated code to do:

  • The ordinal value of ‘a’ (i.e. 97) is subtracted from the character passed to the function.
  • As a result for characters between ‘a’ and ‘r’, you’d see values between 0 and 17 (both included).
  • For each value between 0 and 17, the compiler then specified the label of the instruction that the execution should jump to, in the IL switch statement.
  • Notice IL_0059: if the value does not lie between 0 and 17, code has been generated to directly jump execution to IL_0073 and we get ready to return “something default”.

Sweet, isn’t it?

Does the compiler always handle “switch” this way?

Nope. If the range of the values specified in the case clauses is large, creating a table as explained previously, will cause wastage of memory. So a function like the following won’t be utilizing the IL “switch” statement and will be treated like a sequence of “if-then-else” statements. However, for efficiency, the order of comparisons might be different from the order in which “case” clauses were specified in your code (more info).

public string DoSomethingElse(int bla)
{
switch (bla)
{
case 10:
case 45:
case 99:
return “something 1″;
case 21:
case 1091:
return “something 2″;
case 2048:
case 256:
case 1072:
return “something 3″;
default:
return “something default”;
}
}

Here’s a link to an article that presents some findings from comparing execution speed of “switch” against “if-else-if”. But like I said earlier, this qualifies as micro-optimization and there’re most probably other things in your code you should be worrying about.

About these ads
Posted in: .NET, General, Tech/Hacks