11

Is there a reason I am missing that a block within a case statement isn't considered a block level declaration space?

I keep getting an error (variable has already been declared) when I try

case x:
  var someVariable = 42;
break;
case y: 
   var someVariable = 40;
break;

but I can do

case x:
   try{var someVariable = 42;}catch{} 
break;
case y: 
    try{var someVariable = 40;}catch{}
break;

If C# allowed fall through statements, that would make sense, but it doesn't, and I can't think of a scenario where you can declare a variable in a case statement and use it outside of that block.

Jeff B
  • 8,572
  • 17
  • 61
  • 140
kemiller2002
  • 113,795
  • 27
  • 197
  • 251
  • 2
    Duplicate: http://stackoverflow.com/questions/864153/c-switch-variable-initialization-why-does-this-code-not-cause-a-compiler-error/864183#864183 – Noldorin Jul 02 '09 at 14:13
  • And another: http://stackoverflow.com/questions/222601/variable-declaration-in-c-switch-statement – LukeH Jul 02 '09 at 14:16
  • The edit clarifies what you're looking for. That said, there is a very limited fall through for C# case statements, but it ONLY applies when the cases you want to fall through have no statements of their own. – John Rudy Jul 02 '09 at 14:24

6 Answers6

41

UPDATE: This question was used as the inspiration for this blog post; see it for further details.

http://ericlippert.com/2009/08/13/four-switch-oddities/

Thanks for the interesting question.


There are a number of confusions and mis-statements in the various other answers, none of which actually explain why this is illegal. I shall attempt to be definitive.

First off, to be strictly correct, "scope" is the wrong word to use to describe the problem. Coincidentally, I wrote a blog post last week about this exact mis-use of "scope"; that will be published after my series on iterator blocks, which will run throughout July.

The correct term to use is "declaration space". A declaration space is a region of code in which no two different things may be declared to have the same name. The scenario described here is symptomatic of the fact that a switch section does not define a declaration space, though a switch block does. Since the OP's two declarations are in the same declaration space and have the same name, they are illegal.

(Yes, the switch block also defines a scope but that fact is not relevant to the question because the question is about the legality of a declaration, not the semantics of an identifier lookup.)

A reasonable question is "why is this not legal?" A reasonable answer is "well, why should it be"? You can have it one of two ways. Either this is legal:

switch(y)
{
case 1:  int x = 123; ... break;
case 2:  int x = 456; ... break;
}

or this is legal:

switch(y)
{
case 1:  int x = 123; ... break;
case 2:  x = 456; ... break;
}

but you can't have it both ways. The designers of C# chose the second way as seeming to be the more natural way to do it.

This decision was made on July 7th, 1999, just shy of ten years ago. The comments in the notes from that day are extremely brief, simply stating "A switch-case does not create its own declaration space" and then giving some sample code that shows what works and what does not.

To find out more about what was in the designers minds on this particular day, I'd have to bug a lot of people about what they were thinking ten years ago -- and bug them about what is ultimately a trivial issue; I'm not going to do that.

In short, there is no particularly compelling reason to choose one way or the other; both have merits. The language design team chose one way because they had to pick one; the one they picked seems reasonable to me.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Thanks for the response. Really my question stemmed from the fact that I was curious if I was missing something deeper, like "you can't do it this way because of x." The idea of "we had to make a decision and this was it," seems like a perfectly valid answer and can accept that it came down to the fact a decision had to be made. – kemiller2002 Jul 03 '09 at 00:10
  • The way the language designers chose to define it, you can always create a new declaration space by bracing within the case statements to get behaviour 1. If you originally go for behaviour 1, there is no way to get to behaviour 2. – Sam Meldrum Dec 29 '09 at 17:17
  • Then `var` came along. Add `Tuple` to the mix and you have a problem. An unfortunate result of a decision made a long time ago, one they couldn't have foreseen though. – user247702 Dec 12 '13 at 10:59
  • 20 years later, still hoping that at some point in the future, someone from the original team will shed some light on the decision. – Daniel Dror Jan 01 '20 at 13:31
  • @Eric I agree that variables should be scoped to whole switch block (since we have the other option with case blocks), but C# should allow cleaner code, making this not confusing by letting us declare uninitialized variables before the first case label: `switch (a) { int x; case 0: x = 1; ... break; case 1: x = 9; ... goto case 0; }`. I've open a proposal [here](https://github.com/dotnet/csharplang/discussions/7359). – geekley Jul 19 '23 at 22:10
10

Ah - you don't have fall through, but you can use goto to jump to another labelled case block. Therefore the blocks have to be within the same scope.

NeedHack
  • 2,943
  • 3
  • 30
  • 44
  • +1 for pointing out the pragmatic reason why this notation is not possible. – Brian Jul 02 '09 at 14:19
  • +1 because I'd suspected, but never tested and actually didn't know with 100% certainty, that goto would work with case labels. (Cementing my wishy-washy assertion in my own response that case labels are, indeed, labels. :) ) – John Rudy Jul 02 '09 at 14:25
  • here's an example on my blog (Duff's Device in C#)http://hackersbasement.com/?p=31 – Matthew Whited Jul 02 '09 at 15:51
  • 2
    I'm not sure I follow your logic. Are you saying that the reason why case blocks do not introduce a scope is so that switch(x){case 1: int y = 123; goto case 2; case 2: y = 456; break; } works without having to redeclare y? – Eric Lippert Jul 02 '09 at 18:40
  • 2
    The reason I ask is because if that's your argument then your logic is faulty. The definite assignment rules would guarantee that you could not successfully pass information from one "scope" to the next via "goto". – Eric Lippert Jul 02 '09 at 19:02
  • 2
    Or perhaps you are making the argument that the labels have to be in scope throughout the block, and therefore the sections cannot define their own scopes? That argument is also faulty; scopes nest. The labels in scope in the block would also be in scope in the cases. (As they are today; if you define your own blocks explicitly you'll see that you can "goto case" out of them.) – Eric Lippert Jul 02 '09 at 20:59
7

You could also do:

case x:
  {var someVariable = 42;}
break;
case y: 
   {var someVariable = 40;}
break;

Essentially the braces create the lexical scope, so without the braces, someVariable is loaded into the symbol table twice. I believe this choice is likely made simply to avoid confusion, and possibly to avoid adding complexity to the building of the symbol table.

Timothy Carter
  • 15,459
  • 7
  • 44
  • 62
  • This is wrong, will cause https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0136?f1url=%3FappId%3Droslyn%26k%3Dk(CS0136), I this a down vote – Raiden Core Apr 12 '21 at 13:33
  • @RaidenCore I don't think this error is related at all. It is the same as if you would try to declare variable inside body of `if()` and that variable would existed already. Like `int a; if(true) { char a; }`. – Tatranskymedved Apr 22 '21 at 10:37
2

Because cases aren't blocks, there are no curly braces denoting the scope. Cases are, for lack of a better word, like labels.

You're better off declaring the variable outside the switch() statement and using it thereafter. Of course, in that scenario, you won't be able to use the var keyword, because the compiler won't know what type to initialize.

John Rudy
  • 37,282
  • 14
  • 64
  • 100
  • My question is why isn't it considered a block. Why do you have to force it to be one? – kemiller2002 Jul 02 '09 at 14:17
  • Scope blocks are never automatic in C#. They don't exist by default. Scope blocks are ALWAYS defined with curly braces. You may notice that scope takes effect in for, foreach, if, etc., statements -- WHEN you use curly braces to denote the scope block. With the aforementioned statements, if you omit the curly braces, they only operate on the very next line -- because there is no scope without definition of start and end via curly braces. Since case does not require curly braces, unless you decide to insert them, case statements use the nearest scope, which is the switch statement. – John Rudy Jul 02 '09 at 14:22
  • Yeah that makes sense now. I didn't think of them as labels, and was curious why yo uwould need to have a {} to force scope, when the break statement could be used to determine the end of the case statement. Always having to force scope makes sense as this would be a break in the overall structure of the language also. – kemiller2002 Jul 02 '09 at 14:32
  • 2
    John Rudy, your analysis, though plausible, is not actually correct at all. It is simply not the case that scopes are always bounded by curly braces in C#. The foreach loop DOES introduce a new scope, even if the body has no braces. Scope is extremely carefully defined in the C# specification; I encourage you to read it thoroughly if you want to correct your understanding of what exactly the rules are for introducing new scopes in C#. – Eric Lippert Jul 02 '09 at 18:32
1

As of C# 8.0 you can now do this with switch expressions

you use the switch expression to evaluate a single expression from a list of candidate expressions based on a pattern match with an input expression

var someVariable = caseSwitch switch {
    case x => 42,
    case y => 40,
    _ => throw new Exception("This is how to declare default")
};
Dystar
  • 97
  • 8
-1

You could just declare the variable outside the scope of the switch statement.

var someVariable;

switch();
case x:   
someVariable = 42;
break; 
case y:     
someVariable = 40;
break;
Nate
  • 408
  • 3
  • 11
  • You can't do that, because compiler doesn't know what type `someVariable` is. You must do at least `var someVariable = default(int);` . Also you have typo in the code, there is `;` after `switch()`. – Tatranskymedved Apr 22 '21 at 10:30