64

Why does C# allow this:

var s = "Nice";
switch (s)
{
    case "HI":
        break;
    const string x = "Nice";
    case x:
        Console.Write("Y");
        break;
}

But not this:

var s = "Nice";
switch (s)
{
    const string x = "Nice";
    case x:
        Console.Write("Y");
        break;
}
René Wolferink
  • 3,558
  • 2
  • 29
  • 43
rtuner
  • 2,362
  • 3
  • 25
  • 37
  • 8
    Any other language allows it? – Rajan Panneer Selvam May 22 '13 at 14:18
  • 50
    why would you want to do either? – Jodrell May 22 '13 at 14:18
  • 12
    Whether or not it's likely someone would write code like this, it's still an interesting question. There must be some strange scoping stuff going on behind the scenes. – Stealth Rabbi May 22 '13 at 14:20
  • 1
    @Jodrell: Was trying to understand how switch scopes work. I had a variable declared inside a case statement and it was saying it was already declared in another case statement. Then I tried to move it outside the case statement, and I landed here. – rtuner May 22 '13 at 14:22
  • 1
    @StealthRabbi not really, its just illegal to have other statements between `switch` and the first `case`. The second block works because `const` statements are not executed at runtime anyway. – Jodrell May 22 '13 at 14:24
  • I really wonder why you'd want to do `const string x = "Nice"; case x:`, when you can do `case "Nice":` just as well. Not only is it less code, it's also easier to read and doesn't even impact performance in a negative way. – Nolonar May 22 '13 at 14:24
  • Interesting enough, Visual Studio said that only constant are allowed in case statements, I just moved on to try a constant. I don't get that either. – rtuner May 22 '13 at 14:27
  • 7
    @rtuner The reason the case statements need to be compile time constants is because the implementation of `switch` is a dictionary, not a series of `if/else if` statements. It needs them to result in an object which can be the key of the dictionary. It's also important that there be no side effects of evaluating the case since those side effects wouldn't be generated when testing the cases as they would be in, say, C++. – Servy May 22 '13 at 14:30
  • 1
    I always thought it odd that you don't need to put curly braces around your code when you have more than one statement after a case. It kind of makes switch statements feel more like a goto than an if. – Martin Brown May 22 '13 at 14:33
  • 3
    @MartinBrown That's because they more or less are (logically, not in implementation; in implementation it's a dictionary, as I said), and also because there isn't actually an inner scope for each `case` statement, unless you use braces. This compiles and runs fine: `case "a": string a = ""; break; case "b": string b = a;break;`. – Servy May 22 '13 at 14:43
  • 1
    Scrub my previous assertion about the scoping rules of `const` http://www.jaggersoft.com/csharp_standard/15.5.2.htm, However, a `switch` is a scoping block and a `case` is not, so a variable declared in an earlier `case` of the same switch is still in scope. The perculair thing about `const` is, it doesn't have to be reachable to be valid. That makes sense because they are not executed at runtime. http://stackoverflow.com/questions/1074589/case-statement-block-level-declaration-space-in-c-sharp – Jodrell May 22 '13 at 14:45
  • 10
    Thanks for posting this question; I'm going to add this one to my list of switch oddities and my list of subtle Mono errors. If you're interested in unusual uses of the switch statement see my article on that subject: http://blogs.msdn.com/b/ericlippert/archive/2009/08/13/four-switch-oddities.aspx – Eric Lippert May 22 '13 at 15:24
  • What da??? Thanks for that post @EricLippert. Learnt a lot of new stuff. Thanks – rtuner May 22 '13 at 16:29

3 Answers3

120

Because your indentation is misleading, the first code actually is:

var s = "Nice";
switch (s)
{
    case "HI":
        break;
        const string x = "Nice";
    case x:
        Console.Write("Y");
        break;
}

That is, x is declared inside a case statement (though after a break), where it is valid. However, directly inside a switch statement it’s invalid – the only valid statements there are case and default.

Furthermore, const declarations are evaluated at compile time, so x is defined even though there’s a break statement before.

However, note that the Mono C# compiler will not compile this code, it complains that “the name ‘x’ does not exist in the current scope” so Mono seems to implement more checks than the .NET compiler. However, I can’t find any rules in the C# standard which forbid this use of the const declaration so I assume that the .NET compiler is right and the Mono compiler is wrong.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    But how come it displays y, if it does actually break? – rtuner May 22 '13 at 14:19
  • 1
    @rtuner I guess because the `const` declaration is moved to the top of the method at compile time. – CodeCaster May 22 '13 at 14:20
  • 21
    @rtuner `const` statements are not executed at runtime, they are substituted at compile time. Try and put a break point on it. – Jodrell May 22 '13 at 14:20
  • @rtuner because it's a constant, it's assignment is done statically, no when it arrives at that line. – René Wolferink May 22 '13 at 14:21
  • 6
    But _why_ is any code allowed after the break and before the next case? – Magnus May 22 '13 at 14:23
  • 2
    @Magnus because it will lead to "unreachable" code, which is not invalid at that location. – CodeCaster May 22 '13 at 14:24
  • 14
    @Magnus Why are you allowed to put code after a `return;` and before the end of that scope? It's code that won't run. The answer is simply because they didn't bother to make it illegal; making it illegal was more work than just leaving it, and leaving it doesn't really cause problems. The feature request to prohibit it just isn't worth the effort to implement. – Servy May 22 '13 at 14:25
  • The problem I have with this answer is that: `case HI": break; string x = "Nice";` is invalid, so I don't think you're correct Magnus. – Chibueze Opata May 22 '13 at 14:30
  • 2
    @ChibuezeOpata The only reason that's illegal is because with that change the second case wouldn't evaluate to a compile time constant. If you don't use `x` in the second case then the code compiles just fine, even with that change. That shows that this answer *is* right. – Servy May 22 '13 at 14:32
  • @ChibuezeOpata Is it invalid? The Mono compiler compiles it and nothing in the standard forbids it. If the .NET C# compiler rejects it, report a bug. – Konrad Rudolph May 22 '13 at 14:43
  • @KonradRudolph I compiled it; it compiles fine. – Servy May 22 '13 at 14:44
  • Hmmmm? you compiled `case HI": break; string x = "Nice";`??? I am saying the answer is a bit misleading since you tend to think it's valid to write something after break, but it's not. – Chibueze Opata May 22 '13 at 14:45
  • 2
    @ChibuezeOpata The implementation is that a case statement can be followed by any number of statements. It actually does do a reachability check, and it will generate a warning saying that the given line is unreachable, but it doesn't go out of it's way to generate an error even though it detects unreachable code. Why should they spend extra time and effort to change that warning into an error? What does it gain? If you can justify that, then go ahead and propose it to Microsoft. – Servy May 22 '13 at 14:46
  • @ChibuezeOpata In what way is it not valid? – Servy May 22 '13 at 14:47
  • @ChibuezeOpata [Yes, it compiles](http://ideone.com/Ofb1CW) (add the missing quote though: `case "HI"`) – Konrad Rudolph May 22 '13 at 14:47
  • Seems, you're all getting this wrong, `case "HI": break; string x = "Nice"; case x: Console.Write("Y"); break;` doesn't compile. – Chibueze Opata May 22 '13 at 14:48
  • 1
    @ChibuezeOpata Correct, for reasons that [I've already explained to you](http://stackoverflow.com/questions/16694189/why-does-c-sharp-allow-statements-after-a-case-but-not-before-it#comment24027050_16694227). It has nothing to do with the fact that it's unrechable; it's because `case` statements must be compile time constants. If you change the second case to `case "bye":break;` then it compiles just fine. – Servy May 22 '13 at 14:50
  • 22
    To address your last paragraph: indeed, Mono appears to be in the wrong here. But one can hardly blame them; this is a bizarre scenario. I wonder if Mono gets other rules about scoping inside switch statements wrong? I shall find out! – Eric Lippert May 22 '13 at 15:26
  • 1
    I agree with @Eric Lippert--Mono appears to be wrong. Also, I'm surprised that the compiler didn't at least issue a warning about unreachable code--but, since it's a `const` that's being defined, which as someone else mentioned gets replaced at compile-time with its defined value, then that statement probably just gets eliminated and you simply end up with `case "Hi": break; case "Nice" /* Substituted for 'x' by the compiler */: ...`. – fourpastmidnight May 22 '13 at 15:51
  • 2
    @fourpastmidnight: Right, there's nothing executable there to be unreachable, so the warning is suppressed. – Eric Lippert May 22 '13 at 16:14
7

Because the language specification does not allow a const directly in your switch (only case and default are allowed):

switch (expression)
{
   case constant-expression:
      statement
      jump-statement
   [default:
      statement
      jump-statement]
}

Where:

expression: An integral or string type expression.
statement: The embedded statement(s) to be executed if control is transferred to the case or the default.
jump-statement: A jump statement that transfers control out of the case body.
constant-expression: Control is transferred to a specific case according to the value of this expression.

In the first case the const is part of your case logic. The const will is only working because it is rewritten at compiletime and not at runtime.

Alex Essilfie
  • 12,339
  • 9
  • 70
  • 108
Peter
  • 27,590
  • 8
  • 64
  • 84
1

... because switch does this

jump_to_the_label_matchig(s)
{
   label1:
      ...
      done_quit_this;
   label2:
      ...
      done_quit_this;
   d'oh:
      ...
      done_quit_this;
}

and not this

now_jump_to_the_label_matchig(s)
{

   le'mme_wander_around_doing_things_that_could_have_been_done_before_me;

   label1:
      ...
      done_quit_this;
   label2:
      ...

I betcha that if that was allowed, you'd find people willing to do all their programming in there :-)

G. Stoynev
  • 7,389
  • 6
  • 38
  • 49