Search

Friday, December 15, 2006

Why can we only use constants in a switch-case statement?

Why can we only use constants in a switch-case statement? The following code fails to compile with the error “A constant value is needed” for someStr as it is not a constant string.


static void func(string str)
{
switch(str)
{
case "Zaphod": Console.WriteLine("The king"); break;
case someStr: Console.WriteLine("The coder"); break;
default: Console.WriteLine("None"); break;
}
}
string someStr = "Noo";

Here goes a long answer to this short question.



The reason is simple and yet involved. Let’s take the following valid code which only has constants and see how it works.


static void func(string str)
{
switch(str)
{
case "Zaphod": Console.WriteLine("The king"); break;
case "Abhinab": Console.WriteLine("The coder"); break;
default: Console.WriteLine("None"); break;
}
}

If we open see the code in IL it looks something like this


      L_0007: ldstr "Zaphod"
L_000c: call bool string::op_Equality(string, string)
L_0011: brtrue.s L_0022
L_0013: ldloc.0
L_0014: ldstr "Abhinab"
L_0019: call bool string::op_Equality(string, string)
L_001e: brtrue.s L_002f
L_0020: br.s L_003c
L_0022: ldstr "The king"
L_0027: call void [mscorlib]System.Console::WriteLine(string)
L_002c: nop
L_002d: br.s L_0049
L_002f: ldstr "The coder"
L_0034: call void [mscorlib]System.Console::WriteLine(string)
L_0039: nop
L_003a: br.s L_0049
L_003c: ldstr "None"

See the usage of op_Equality in L_000C and L_0019. This indicates that even though we are using switch-case, ultimately the code is converted to multiple if-then-else by the compiler. So the switch-case is converted by the compiler to something like


if (str == "Zaphod")
Console.WriteLine("The king");
else if (str == "Abhinab")
Console.WriteLine("The coder");
else
Console.WriteLine("None");

If this is the case then what is stopping the case statements from having non-constants? In case of a non-constant the code generated could be something like if (str == someNonConstantStr) which is valid code.



The answer is simple. When the numbers of cases are larger, the generated code is very different and is not constituted of if-then-else. Isn’t that obvious? Otherwise why would anyone ever use switch-case and why would we call switch case to be faster??



Lets see when we have a large number of case’s as follows what happens.


switch(str)
{
case "Zaphod": Console.WriteLine("The king"); break;
case "Abhinab": Console.WriteLine("The coder"); break;
case "Ford": Console.WriteLine("The Hitchhiker"); break;
case "Trilian": Console.WriteLine("The traveler"); break;
case "Marvin": Console.WriteLine("The Robot"); break;
case "Agrajag": Console.WriteLine("The Dead"); break;
default: Console.WriteLine("None"); break;
}

For this first a class is generated by the compiler which looks like


Internal class CompilerGeneratedClass
{
internal static Dictionary CompilerGenDict;
}

And then for the switch case the following code is generated.


if (CompilerGeneratedClass.CompilerGenDict == null)
{
Dictionary dictionary1 = new Dictionary(6);
dictionary1.Add("Zaphod", 0);
dictionary1.Add("Abhinab", 1);
...
CompilerGeneratedClass.CompilerGenDict = dictionary1;
}
if (CompilerGeneratedClass.CompilerGenDict.TryGetValue(
str, out num1))
{
switch (num1)
{
case 0: Console.WriteLine("The king"); return
case 1: Console.WriteLine("The coder"); return;
case 2: Console.WriteLine("The Hitchhiker"); return;
...
}
}
Console.WriteLine("None");

What this means is that first time the function is called a Dictionary of strings (key) and int (value) is created and all the cases are stored in this dictionary as the key and an integer as a value is stored against it. Then for the switch statement the string is taken and is queried in the dictionary and if it is present the number value for the string is returned. Using this number the compiler creates an efficient jump table and it jumps to the target Console.Writeline string.



Now the answer :). The strings are pre-stored in the dictionary. If the strings in the case statement were not constants, changes in them won’t reflect in the dictionary and hence you’d land up comparing against stale value. To avoid this inconsistency the non-constant values are not supported at all.



Obviously for dynamic values the dictionary cannot be used and hence there is no optimization possible for switch-case so one should anyway use if-then-else.

No comments: