Jaa


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.

Comments

  • Anonymous
    December 12, 2006
    The comment has been removed
  • Anonymous
    December 12, 2006
    The compiler could support variable string switches by exclusively using the if / else pattern for variable strings. Then, for constant string switches use either if / else or dictionary pattern as it presently does now. It seems pointless to restrict this scenario when it can so easily be overcome by manually using verbose if / else statements.
  • Anonymous
    December 12, 2006
    Well, this isn't a static switch then anymore.A static switch is a jump-table, which is fast:)You can have dynamic switches like this:using (var s = new Switch()){s["item1"] = delegate { /* action 1 / };s["item2"] = delegate { / action 2 / };s[e.MyValues] = delegate { / action 3 */ };}Ofcourse you have to implement it first, which is a no brainer.
  • Anonymous
    December 12, 2006
    The comment has been removed
  • Anonymous
    December 12, 2006
    C#, like all languages, allows you do write efficient and inefficient code. If there is a major bottleneck, it will show up in a profiler. In this case, C# only mitigates the problem by forcing you to write verbose if / else statements instead of a concise switch statement.I prioritize readability over efficiency. Of course I then profile the code and rework troublesome parts for efficiency. Premature optimizations are often not worth it.If the switch syntax will not allow what I want, then I will rewrite it with if / else statements. It is all the same in the end, only the compiler gets in my way. I do not see any real merit in this imposed restriction.
  • Anonymous
    December 12, 2006
    I agree with Oidon. Since it results in no worse performance anyway, I should atleast be able to get the clear readability benefit from the switch/case.
  • Anonymous
    December 13, 2006
    The comment has been removed
  • Anonymous
    December 13, 2006
    After programming for almost two decades now, I must admit that I have never thought of (or even noticed) switch as being more efficient than if / else. Optimization details are left up to the compiler and vary greatly. My main responsibility is to write clear, bug-free code. Others will read my code. And I need to read others code. Readability is important. As oidon mentioned, a profiler will indicate areas that need more work. It is not something that I need to "catch" or second-guess without evidence to the contrary.Besides, you can't switch on a string in C/C++, so that argument is hardly relevant.Without other evidence to the contrary, I am inclined to think that this part of C# was poorly designed.
  • Anonymous
    December 13, 2006
    The comment has been removed
  • Anonymous
    December 13, 2006
    eman, oidon why do you think C# is badly designed here. C, C++ behaves exactly the same way even when it switches on int via jump  tables. Both need literals here.
  • Anonymous
    December 14, 2006
    The comment has been removed
  • Anonymous
    December 14, 2006
    Sad.  Too bad C# can't have any way to write compile-time code (preprocessor macros, functional eager calculation, etc.)  In languages where you could do that, "const" would be less of a problem... still, either way this means that you can never use a linebreak in switchable text if your code is cross-platform, since environment.newline is evaluated at run-time and thus is unusable for this purpose.
  • Anonymous
    December 14, 2006
    Actually now to come to think about it, one could precompile my example to a single function of bytecode.See the class System.Reflection.Emit.DynamicMethodA switch block has the ability of a fall thro via goto next case, which the if/else does not have.By precompiling my example there will be no significant overhead.I think this will work in a full trusted evironment only.:)
  • Anonymous
    January 11, 2007
    [quote JonInNC]One of the primary goals of the designers of C# was to encourage code that was easy to understand(which is why there are no macro black magic, and no typedefs).[/quote]The typedef keyword is gone, but type aliasing is still available with the "using" keyword. See http://msdn2.microsoft.com/en-us/library/sf0df423.aspx.I don't know why you think removing type aliasing would make code easier to understand. Especially when using generics, type aliasing makes code much more readable.
  • Anonymous
    September 17, 2007
    The comment has been removed