Four switch oddities


The C# switch statement is a bit weird. Today, four quick takes on things you probably didn’t know about the switch statement.

Case 1:

You probably know that it is illegal to “fall through” from one switch section to another:

switch(attitude)
{
  case Attitude.HighAndMighty:
    Console.WriteLine(“High”);
    // we want to fall through, but this is an error
  case Attitude.JustMighty:
    Console.WriteLine(“Mighty”);
    break;
}

But perhaps you did not know that it is legal to force a fall-through with a goto:

switch(attitude)
{
  case Attitude.HighAndMighty:
    Console.WriteLine(“High”);
    goto case Attitude.JustMighty;
  case Attitude.JustMighty:
    Console.WriteLine(“Mighty”);
    break;
}

Pretty neat! Cases are semantically just labels to which the switch does a conditional branch; we let you do an explicit branch if you want to.

Case 2:

A common and confusing error that C programmers using C# (like me!) make all the time:

switch(attitude)
{
  case Attitude.HighAndMighty:
    Console.WriteLine(“High and Mighty”);
    break;    
  case Attitude.JustMighty:
    Console.WriteLine(“Just Mighty”);
    break;
  default:
    // Do nothing
}

That’s an error because in the default case, you “fall through”. Admittedly, there is nothing to “fall through” to, but the compiler is picky about this one. It requires that every switch section, including the last one, have an unreachable end point. The purpose of this rule, and of the no-fall-through rule in general, is that we want you to be able to arbitrarily re-order your switch sections without accidentally introducing a breaking change. Fix it by making the “do nothing” explicit with a break statement.

This is particularly confusing because some people interpret the error message as saying that the problem is falling into the default case, when actually the problem is that you’re falling out of the default case.

Case 3:

As I discussed earlier, a declaration space is a region of code in which two declared things may not have the same name. The foreach loop implicitly defines its own declaration space, so this is legal:

foreach(var blah in blahs) { … }
foreach(var blah in otherblahs) { … }

Switch blocks also define their own declaration spaces, but switch sections do not:

switch(x)
{
  case OneWay:
    int y = 123;
    FindYou(ref y);
    break;
  case TheOther:
    double y = 456.7; // illegal!
    GetchaGetcha(ref y);
    break;
}

You can solve this problem in a number of ways; the easiest is probably to wrap the body of the switch section in curly braces:

switch(x)
{
  case OneWay:
  {
    int y = 123;
    FindYou(ref y);
    break;
  }
  case TheOther:
  {
    double y = 456.7; // legal!
    GetchaGetcha(ref y);
    break;
  }
}

which tells the compiler “no, really, I want these to be different declaration spaces”.

If you have a variable that you want to declare once and use it in a bunch of different places, that’s legal, but a bit strange:

switch(x)
{
  case OneDay:
    string s;
    SeeYa(out s);
    break;
  case NextWeek:
    s = “hello”; // legal, we use the declaration above.
    Meetcha(ref s);
    break;
  }
}

That looks a bit weird, I agree, but it also looks a bit weird to have one switch block with two unbraced competing declarations in it. There are pros and cons of each, the design team had to pick one way or the other, and they chose to have switch cases not define a declaration space.

Case 4:

A funny consequence of the “reachability analysis” rules in the spec is that this program fragment is not legal:

int M(bool b)
{
  switch(b)
  {
    case true: return 1;
    case false: return 0;
  }
}

Of course in reality you would probably write this as the far more concise “return b ? 1 : 0;” but shouldn’t that program be legal? It is not, because the reachability analyzer reasons as follows: since there is no “default” case, the switch might choose some option that is not either case. Therefore the end point of the switch is reachable, and therefore we have an int-returning method with a reachable code path that falls off the end of the method without returning. 

Yeah, the reachability analyzer is not very smart. It does not realize that there are only two possible control flows and that we’ve covered all of them with returns. And of course, if you switch on a byte and have cases for each of the 256 possibilities, again, we do not detect that the switch is exhaustive.

This shortcoming of the language design is silly, but frankly, we have higher priorities than fixing this silly case. If you find yourself in this unfortunate case, just stick a “default:” label onto one of the sections and you’ll be fine.

— Eric is on vacation; this posting was prerecorded. —

Comments (39)

  1. carlos says:

    I often need to use braces under case statements (Case 3 above) in both C# and C++.  But the IDE insists on indenting the braces, so the code under the case is double indented.  Which has nothing to do with the language design, but is a constant irritant.

  2. Random832 says:

    If fall through is illegal, why is the break statement required to not fall through?

  3. Tony Cox [MSFT] says:

    @Random832:

    Are you suggesting that there should be an implied break? I think that would be potentially confusing – you might think you’re getting fall-through (especially if you come from a C/C++ background), but instead you’re getting a break, with no indication from the compiler that you’re might not be getting what you intended.

    With the way it works now, there’s no possiblity for confusion.

    Basically, you want to make it so that if it compiles succesfully, there’s the maximum possible chance that you’re getting the expected behavior.

  4. My Twopence says:

    Two things I find particularly annoying about the C# switch statement:

    1) Having to enter break for every single case – since fall-through has to be explicitly declared (Case 1), why not just assume an implicit break if no explicit break, return etc. exists ?

    2) Not being able to specify a range e.g. case "A" to "Z", or case < 5, rather than having to explicitly declare every single case.

    Three other non-switch related gripes – is it unreasonable to expect a modern language to have:

    1) a built-in version of String.Replace that is case-insensitive ?

    2) a built-in version of Math.Truncate that allows you to specify decimal places (i.e. truncate the number to 2 decimal places WITHOUT rounding) ?

    3) a built-in arbitrary precision number type / library ?

    While LINQ, lambda expressions, delegates etc. are very cool, sometimes I find the lack of basic functionality exasperating.  

  5. Anon says:

    @My Twopence – explicit break is required as per re-ordering switch use-case justification in Case 2

    "The purpose of this rule, and of the no-fall-through rule in general, is that we want you to be able to arbitrarily re-order your switch sections without accidentally introducing a breaking change."

  6. Trevel says:

    "With the way it works now, there’s no possiblity for confusion."

    — obviously there is or he wouldn’t have had to make this post. And as someone who has used various flavours of Basic, there’s no confusion in not having breaks and IS in having them; why am I putting in something to say not to do something that I can’t do in the first place? If the Break statement is mandatory, why not just leave it out?

    I can understand why they were left in — approaching from a C perspective where fall-through is assumed, it makes sense to say explicitly that that isn’t happening. Approaching from a Basic perspective, where fall-through does not occur, it doesn’t make any sense to include a keyword that IMPLIES fall-through is possible, when it really isn’t.

    In other words, from my perspective, the word "break" is only there as a trap for the unwary. It doesn’t do anything but break your build if you leave it out.

  7. David Nelson says:

    "it doesn’t make any sense to include a keyword that IMPLIES fall-through is possible, when it really isn’t"

    I have to agree with this. I would also point out that we aren’t forced to specify "return" at the end of a method that doesn’t have a return parameter.

    I understand that the C# team thought they were making things easier for those with a C background, but at the same time they made things harder for those coming from other backgrounds (or no background). And honestly, once you have spent just a little time in the language and learn how it is different from C, the value of the explicit break is lost very quickly, while still being a constant annoyance.

  8. Eric TF Bat says:

    In using Delphi (C# 0.1 as I like to call it) I got used to this idiom:

    case BooleanVar of

     True:

       Result := 1;

     else // False:

       Result := 0;

    end;

    This is the same as the C#:

    switch (booleanVar) {

     case true:

       return 1;

     default: // case false:

       return 0;

    }

    It’s a habit I got into to avoid the compiler complaining about the Result not being set (a DailyWTF reader would assume it was allowing for TRUE, FALSE and FILE_NOT_FOUND…) but I see it will be useful for my future C# work as well.

    Incidentally, Delphi also has the ability to specify groups and ranges, as in:

    case C of

     ‘A’,’E’,’I’,’O’,’U’: WriteLn(‘Vowel!’);

     ‘0’..’9′: WriteLn(‘Digit!’);

     else WriteLn(‘Something Else!’);

    end;

    Gnu C has a similar option.  It’s a shame C# doesn’t.

    (Oh, and: you don’t have a preview button on this blog.  So if your comment formatter isn’t smart enough to handle indentations and line breaks, this comment is going to be gibberish, which I’m afraid is your fault.  Blog commenting is a solved problem; failure to provide all the appropriate features is wilful folly.)

  9. Tony Cox [MSFT] says:

    The decision about whether to have implicit breaks or to require them explicitly boils down to which problem you think is worse:

    a) You omit a break that was required, and your code doesn’t compile

    b) You expected to get fallthrough, but instead you got a break

    I submit that the first problem is a better one to have. It doesn’t compile, so you immediately know you did it wrong, and it’s also pretty obvious from the error message how to fix it.

    With the second problem, you could quite happily check-in code which compiles just fine, and which has a bug in it which you will only discover later when you run test cases which exercise those code paths.

  10. ZagNut says:

    Theoretically, case 3 = bad compiler design to even allow that.  Consider:

    static void Main(string[] args)

    {

    string t = string.Empty;

    int i = 1;

    switch (i)

    {

    case 0:

    string s;

    s = "paco";

    t = s;

    break;

    case 1:

    s = "taco";

    t = s;

    break;

    }

    Console.WriteLine(t);

    Console.ReadKey();

    }

    This compiles AND works.  string s should be an unknown variable in case 1, but also since the program works, we see that s is allocated memory even though case 0 is never called to initialize it.  Wtf?

  11. rtm242 says:

    I’m surprised the one legal fall-through case was not mentioned above:

               int i = 0;

               switch (i)

               {

                   case 0:

                   case 1:

                       // legal!

                       break;

                   case 2:

                       break;

                   default:

                       break;

               }

    I’m assuming the grouped case statements are considered one section which is why this works.

  12. Dave says:

    I don’t know whether it’s sad or fortunate that C# will never know the brilliance of Duff’s Device.

  13. Anon says:

    @David. Saying because there’s a break statement there’s an implication of fall-thought is a little strong. Maybe all that’s needed is a better compiler message "ERROR: All cases require break or return, even default. Fall-through is not permitted." to clear up the confusion.

    Like it or not, C# comes from a C heritage, and while it’s anachronistic to some, especially 10 years or so later, I find the explicitness of switch better than new implied rules that contradict old habits.

    The fact that it never falls through is a new feature to the old switch statement, and is always required mainly because so many stupid bugs occurred in the past because the ‘implied’ behavior was easily missed, even with  code reviews.

  14. Larry Lard says:

    Justifiable as the current approach is, it does mean that to anyone learning C# who has never learned C or C++ (and the proportion of learners for whom this is true is ever-increasing, I would suggest), your language looks a little bit stupid.

  15. Robert says:

    c# switch is an awful mess. I’d like an updated switch in c# 4 that takes a lambda for its case statements.

    You forgot to mention strange case no #5: You can not switch on type!

    Oh, and in case #2 you say "The purpose of this rule, and of the no-fall-through rule in general, is that we want you to be able to arbitrarily re-order your switch sections without accidentally introducing a breaking change".

    Fine, but doesn’t case 3 stop you from reordering your switch statements? To really be able to reorder arbitrarily, case needs its own declaration space.

  16. Chris Nahr says:

    "You forgot to mention strange case no #5: You can not switch on type!"

    That’s not strange at all, it follows from the basic rule that you can only have cases for compile-time constants, and that doesn’t include type objects.

    Of course, that rule itself is rather annoying and it sure would be nice if we could have cases for arbitrary expressions…

  17. Anders Dalvander says:

    Another argument for case 4 is the protection against hacked bools. 😉

    class Program

    {

      [StructLayout(LayoutKind.Explicit)]

      struct HackedBool

      {

         [FieldOffset(0)]

         int i;

         [FieldOffset(0)]

         bool b;

         public static bool IntToBool(int i)

         {

            HackedBool bh = new HackedBool();

            bh.i = i;

            return bh.b;

         }

      }

      static void Main(string[] args)

      {

         bool b = HackedBool.IntToBool(2);

         switch (b)

         {

            case false:

               Console.WriteLine("False");

               break;

            case true:

               Console.WriteLine("True");

               break;

            default:

               Console.WriteLine("FileNotFound");

               break;

         }

      }

    }

  18. I generally prefer to use else if cascades instead of switch. I find it looks better in code, I can use wierd stuff in conditions, no break statements littering the flow. I would hope a good optimising compiler would produce much the same result anyway.

  19. Jeff Yates says:

    Shouldn’t the third sentence of the first paragraph under the code example of "Case 2" read:

       It requires that every switch section, including the last one, have an *reachable* end point.

    It currently states "unreachable" instead of "reachable".

  20. Random832 says:

    Why does it use the C syntax if it’s not going to allow implied fall through anyway?

    Why not some new syntax? Say, case(1) { … } case(2) { … } like every other control structure ever.

  21. carlos says:

    @Jeff Yates: No.

    The end point can’t be reached because there must be a break statement in the way.  So all end points are unreachable.

    Even without this, you couldn’t insist that all end points be reachable; you might have an unconditional return.

  22. Mario says:

    Something I miss from C++ in the C# switch is the ability to declare a variable in the switch expression.

    It is not unusual to have to use the value we are switching on (especially so in the default case) and being able to store the value in a variable comes in handy.

    The workaround is pretty simple, but in order to avoid pollution of the scope with an additional variable, you have to add another block surrounding the switch. Compare the following:

    C++:

    switch (int nextValue = GetNextValue()) {

     case 0:

       return -1;

     …

     default:

       return nextValue / 2;

    }

    C#:

    {

     int nextValue = GetNextValue();

     switch (nextValue) {

       case 0:

         return -1;

       …

       default:

         return nextValue / 2;

     }  

    }

    The same applies to the while (even though that has a simpler workaround: you can always use a for).

    I’m not complaining or anything, but it would be interesting to know the reason why this limitation was introduced in the language (provided Eric reads this when he gets back from his vacations).

  23. jdu says:

    @My Twopence:

    I support your comment. There are some *core* functions missing.

    1) You can easily do a case-insensitive sort using Regex.Replace instead of String.Replace.

    I really miss two features regarding text in the framework:

    – being able to do accent-insensitive sorts and compares.

    – having a "natural" sort and comparer (sorting "file12" after "file2").

    2) Round has an overload with a number of decimal places to round to. It’s a indeed strange that Truncate (and Ceil, Floor), didn’t get the same option. It’s easy enough to create your own helper method, though.

    3) At least the integer kind will be available in .NET 4 (BigInteger). Still missing an arbitrary precision real number, though.

  24. pminaev says:

    Implicit fallthrough without break is not just a C/C++ thing – it also exists in Java. Given that Java is otherwise much more similar to C#, this could well be a subtle difference that can trip someone in a very nasty way. And between C, C++, and Java, we likely have the majority to be placated anyway, so people with other backgrounds will just have to accept it as an unavoidable legacy wart.

  25. Mark says:

    I vaguely remember in an early beta of C# (ten years ago?) that the switch had implicit breaks and they reverted to C style explicit breaks in a subsequent beta to avoid confusion. It seems a shame to me, but I was young at the time and didn’t have any legacy affecting my judgement.

    @Anon: How does implicit breaks prevent reordering?

    @Tony Cox: In case b that you present, why would you expect to get fallthrough? Only beccause all other C-style languages (C, C++, Java, Javascript, etc) behave that way. I understand the reason, it just seems unfortunate that C# has repeat previous mistakes in the name of conformity.

    It seems that the switch statement was optimized for the rare case of fallthrough. Implicit breaks and explict fallthrough would be better suited for the common case, IMO.

  26. Bullinger says:

    I’m wondering how many programmers are actually using switches. Personally I prefer "if, else if" in almost all cases because I don’t see there many advantages in using switches. "If" even reduces the indentation level, so why and when using switches? Any comments?

  27. TheCPUWizard says:

    Multiple /Cascading if’s being use in place of a switch statement mean a COMPLETELY different thing. The condition must be evaluated multiple times which can give rise to all sorts of issues.

  28. Trevel says:

    One reason for me:

    Clarity.

    An if/elseif block can have a random other check within it. A Switch statement does what it says on the box and nothing else. I find it’s much easier to read, organize, and understand, especially if you have, say, ten or more possibilities.

    Conversely, using a switch means you lose in adaptability — I find that an acceptable compromise to being to tell what an entire block does at a glance, without having to check each term for an additional case.

    In some cases, a compiler might optimize a switch better than the associated if/elseif, but I wouldn’t rely too heavily on that.

  29. Joren says:

    >There are pros and cons of each, the design team had to pick one way or the other, and they chose to have switch cases not define a declaration space.

    This makes sense, but why did they not take the third choice, to simply require writing a declaration space with angular brackets for each case?

    My ideal switch syntax would be like this:

    switch (foo)

    {

       case 1

       {

           Stuff();

           goto case 2;

       }

       case 2

       {

           if (OtherStuff())

               break;

           MoreStuff();

       }

    default

       MoreOtherStuff();

    }

    Every case its own declaration space, which is incredibly convenient. (Conversely, an entire switch being the same declaration space is rather useless, although I agree that without bracketed case labels, it’s the proper choice.) We don’t need any break statements anymore, because they’re implicit in the closing brackets. (Of course we can still use them and case goto’s.) We can also lose the colon.

    In the default case I left out the brackets like you might in an if-statement, because it’s only a single line.

    The biggest advantage is probably that this syntax is far more consistent with the rest of C#. A list of statements that’s logically seperated from the rest of the program is in its own bracketed block, except when it’s only a single non-declaration statement, where you may leave out the brackets.

  30. TheCPUWizard says:

    @Trevel: I agree completely!

    @Joren: In general (there are always exceptions to general rules), I absolutely despise that type of switch statement, it can quickly become nearly impossible to maintain. I strive to maintain a near topological equivilence between switch statements and (psuedo code):

    Dictionary<T, Func<T>> switch;

    switch[T](T);

    Over the long haul (e.g. a prgram that will be in active use/maintenance for a decade or more) simplicity and readability trump most other concenrns.

  31. Joren says:

    @TheCPUWizard:

    Are you talking about jumping from case to case by goto? If so, I dislike that as well, but my post was really only about syntax. The content of the cases was just to give some more color to the example.

    If not, could you please explain what you mean?

  32. I find it interesting the proposed reasoning behind point 2 (that explicit breaks are required even in "default:"–that arbitrarily re-ordering switch sections won’t introduce a breaking change–is completely violated by the syntax in point 3; clearly declaring a variable in one section to be used in subsequent sections inherently is un-re-orderable.

  33. TheCPUWizard says:

    @Joren: The goto threw it over the top, but even the internal branches within a case give me cause for concern in terms of maintainability. In most cases I try to organize a switch statement so that each case is one of the following:

    a) A set of assignments to a group of variables

    b) A set of calls to a group of methods with the method being determined by the value

    c) A call to a method with differing parameters based on the value

    @Peter. I believe the difference is that the effect of allowing fall-through [case #2] is that it would be a COMPILABLE (and therefore runnable) breaking change in program execution, while the latter [case #3] would generate a syntax error, and thus it would just be a compilation error that was easily detectable, and would not result in a runnable program which behaved differently…we will see what Eric has to say when he returns…..

  34. Anon says:

    @Mark Implicit breaks don’t prevent re-ordering. But you’re just trading one inconsistent behavior for another for the sake of 5 extra characters and some explicitness. Obviously there’s a bit of contention on the issue, so explicitness is probably just erring on the side of caution. You can’t please everyone. Personally I just don’t care.

    Maybe you’re all right, they should probably have called it something else than switch,  started from the ground up, and made it do what you want, and not just be a hangover from C++, but when it gets down to needing something fancier, you have the tools to write your own code solution, so the case for the ultraswitch class is lacking that traction.

    I’ve already made a generic class that takes an expression to evaluate, and executes a delegate to achieve the results. You get to do all the things you talk about (no goto or fall-through), and it’s pretty simple to code. To be sure, it was a lot harder before there were generics, but now the class is almost trivially simple.

    Sometimes it’s more important to give you the tools for you to create code that can get the job done (i.e. generics), versus bloating the core language with every possible feature that, in the end, doesn’t really get you that much farther for all the effort.

    @Larry Lard

    Switch is pretty basic, but it basically works, and works well. If you don’t like it, don’t use it I guess. It’s not something to get too upset over. I’m no defender of the faith, but calling a language stupid because it does something different, or has some legacy n00b’s don’t get is, well stupid. All languages have their own baggage of inconsistent stupid things, but as I’m in no position to change them, and can’t be bothered to write my own language (there are already too many IMHO) I just have a cup of tea instead.

  35. @CPUWizard  I mean, if the principle of no fall-through is to facilitate this:

    switch(x)

    {

     case 1:

      DoSomething();

      break;

     case 2:

       DoSomethingElse();

       break;

    }

    … being reorganized to:

    switch(x)

    {

     case 2:

       DoSomethingElse();

       break;

     case 1:

      DoSomething();

      break;

    }

    (which is a laudable principle)  then why that principle didn’t seem to proliferate to the design of other syntax; notably variable declarations in switch scope.  For example:

    switch(x)

    {

     case 1:

      String text = "1234";

      DoSomething(text);

      Console.WriteLine(text);

      break;

     case 2:

       text = "4567";

       DoSomethingElse(text);

      Console.WriteLine(text);

       break;

    }

    … now breaks with an identical re-ogranization:

    switch(x)

    {

     case 2:

       text = "4567";

       DoSomethingElse(text);

      Console.WriteLine(text);

       break;

     case 1:

      String text = "1234";

      DoSomething(text);

      Console.WriteLine(text);

      break;

    }

    Sure, one avoids a logic error and the other doesn’t avoid a compile error; but, I just find it curious that principles cited for reasons /why/ a syntax is the way it is don’t seem to influence other syntax.  i.e. the principle didn’t seem to proliferate through design process for the rest of the syntax (or, we haven’t been made aware of whether or not a concession was made for this particular syntax and why).  Or, it wasn’t an influencing principle at all; just a side-effect of the design which is now used to justify the syntax (which is neither bad nor good; just possible fact).  This is no "answer" to this; we know why it does it this way (the spec says so, that’s the way it was implemented, no one has changed it, more intuitive coming from C/C++, and maybe to be similar with C/C++, etc…)

  36. Jim says:

    I like the explicitly-required break or goto. I’m new to C# but I love it. I’ve made thousands of run-time errors in many other languanges, 🙁

    I prefer compile-time errors over errors that are missed by code inspection or testing, but get found by customers. Not that I’ve ever done that.

  37. quetzalcoatl says:

    this case REALLY looks weird:

    <pre>switch(x)

    {

     case OneDay:

       string s;

       SeeYa(out s);

       break;

     case NextWeek:

       s = "hello"; // legal, we use the declaration above.

       Meetcha(ref s);

       break;

     }

    }</pre>

    due to the spurious end-brace at the end:)

  38. Jeff Yates says:

    Case 2, first paragraph after code sample, 3rd sentence reads "It requires that every switch section, including the last one, have an unreachable end point."

    Don't you mean "reachable end point"?

    No, I mean "unreachable end point". Consider "break;", or "return;" or "goto case default;". In each of those, you "never get to the semicolon", so to speak. The end point of each of those statements is unreachable; the code that immediately follows the semicolon is not the thing that runs next. Compare that to, "M();" – now the semicolon and whatever comes after is reachable, after M() completes. – Eric 

  39. Kip says:

    The final code block in Case 3 has an extra closing brace.