Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I was writing this code:

private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
    switch (criterion.ChangeAction)
    {
        case BindingType.Inherited:
            var action = (byte)ChangeAction.Inherit;
            return (x => x.Action == action);
        case BindingType.ExplicitValue:
            var action = (byte)ChangeAction.SetValue;
            return (x => x.Action == action);
        default:
            // TODO: Localize errors
            throw new InvalidOperationException("Invalid criterion.");
    }
}

And was surprised to find a compile error:

A local variable named 'action' is already defined in this scope

It was a pretty easy issue to resolve; just getting rid of the second var did the trick.

Evidently variables declared in case blocks have the scope of the parent switch, but I'm curious as to why this is. Given that C# does not allow execution to fall through other cases (it requires break, return, throw, or goto case statements at the end of every case block), it seems quite odd that it would allow variable declarations inside one case to be used or conflict with variables in any other case. In other words variables appear to fall through case statements even though execution cannot. C# takes great pains to promote readability by prohibiting some constructs of other languages that are confusing or or easily abused. But this seems like it's just bound to cause confusion. Consider the following scenarios:

  1. If were to change it to this:

    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    case BindingType.ExplicitValue:
        return (x => x.Action == action);
    

    I get "Use of unassigned local variable 'action'". This is confusing because in every other construct in C# that I can think of var action = ... would initialize the variable, but here it simply declares it.

  2. If I were to swap the cases like this:

    case BindingType.ExplicitValue:
        action = (byte)ChangeAction.SetValue;
        return (x => x.Action == action);
    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    

    I get "Cannot use local variable 'action' before it is declared". So the order of the case blocks appears to be important here in a way that's not entirely obvious -- Normally I could write these in any order I wish, but because the var must appear in the first block where action is used, I have to tweak case blocks accordingly.

  3. If were to change it to this:

    case BindingType.Inherited:
        var action = (byte)ChangeAction.Inherit;
        return (x => x.Action == action);
    case BindingType.ExplicitValue:
        action = (byte)ChangeAction.SetValue;
        goto case BindingType.Inherited;
    

    Then I get no error, but in a sense, it looks like the variable is being assigned a value before it's declared.
    (Although I can't think of any time you'd actually want to do this -- I didn't even know goto case existed before today)

So my question is, why didn't the designers of C# give case blocks their own local scope? Are there any historical or technical reasons for this?

share|improve this question
3  
Declare your action variable before the switch statement, or put each case into its own braces, and you will get sensible behavior. –  Robert Harvey Apr 15 '13 at 23:18
3  
@RobertHarvey yes, and I could go even further and write this without using a switch at all -- I'm just curious about the reasoning behind this design. –  p.s.w.g Apr 15 '13 at 23:44
    
if c# needs a break after every case then i guess it has to be for historical reasons as in c / java that is not the case! –  tgkprog Apr 15 '13 at 23:47
    
@tgkprog I believe it's not for historical reason and that the designers of C# did that on purpose to make sure the common mistake of forgetting a break is not possible in C#. –  svick Apr 16 '13 at 0:07
10  
See Eric Lippert's answer on this related SO question. stackoverflow.com/a/1076642/414076 You may or may not come away satisfied. It reads as "because that's how they chose to do it in 1999." –  Anthony Pegram Apr 16 '13 at 0:15

4 Answers 4

up vote 17 down vote accepted

I think a good reason is that in every other case, the scope of a “normal” local variable is a block delimited by braces ({}). The local variables that are not normal appear in a special construct before a statement (which is usually a block), like a for loop variable or a variable declared in using.

One more exception are local variables in LINQ query expressions, but those are completely different from normal local variable declarations, so I don't think there is a chance of confusion there.

For reference, the rules are in §3.7 Scopes of the C# spec:

  • The scope of a local variable declared in a local-variable-declaration is the block in which the declaration occurs.

  • The scope of a local variable declared in a switch-block of a switch statement is the switch-block.

  • The scope of a local variable declared in a for-initializer of a for statement is the for-initializer, the for-condition, the for-iterator, and the contained statement of the for statement.

  • The scope of a variable declared as part of a foreach-statement, using-statement, lock-statement or query-expression is determined by the expansion of the given construct.

(Though I'm not completely sure why is the switch block explicitly mentioned, since it doesn't have any special syntax for local variable declations, unlike all the other mentioned constructs.)

share|improve this answer
1  
+1 Can you provide a link to the scope you're quoting? –  p.s.w.g Apr 16 '13 at 0:12
    
@p.s.w.g You can find a link for downloading the specification on MSDN. (Click the link that says “Microsoft Developer Network (MSDN)” on that page.) –  svick Apr 16 '13 at 0:18
    
So I looked up the Java specs, and switches seem to behave identically in this regard (except for requiring jumps at the end of case's). It seems this behavior was simply copied from there. So I guess the short answer is -- case statements do not create blocks, they simply define divisions of a switch block - and therefore do not have scopes by themselves. –  p.s.w.g Apr 16 '13 at 17:13
1  
Re: I'm not completely sure why is the switch block explicitly mentioned -- the author of the spec is just being picky, implicitly pointing out that a switch-block has a different grammar than a regular block. –  Eric Lippert May 2 '13 at 0:08

But it does. You can create local scopes anywhere by wrapping lines with {}

switch (criterion.ChangeAction)
{
  case BindingType.Inherited:
    {
      var action = (byte)ChangeAction.Inherit;
      return (x => x.Action == action);
    }
  case BindingType.ExplicitValue:
    {
      var action = (byte)ChangeAction.SetValue;
      return (x => x.Action == action);
    }
  default:
    // TODO: Localize errors
    throw new InvalidOperationException("Invalid criterion.");
}
share|improve this answer
    
Yup used this and it works as described –  Mvision Apr 16 '13 at 5:54
1  
+1 This is a good trick, but I'm going to accept svick's answer for coming closest to addressing my original question. –  p.s.w.g Apr 17 '13 at 15:40

A simplified way of looking at scope is to consider scope by blocks {}.

Since switch does not contain any blocks, it cannot have different scopes.

share|improve this answer
3  
What about for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */? There are no blocks, but there are different scopes. –  svick Apr 15 '13 at 23:54
    
@svick: As I said, simplified, there is a block, created by the for statement. switch doesn't create blocks for each case, just at the top level. The similarity is that each statement creates one block (counting switch as the statement). –  Guvante Apr 16 '13 at 0:26
1  
Then why couldn't you count case instead? –  svick Apr 16 '13 at 0:28
1  
@svick: Because case doesn't contain a block, unless you decide to optionally add one. for lasts for the next statement unless you add {} so it always has a block, but case lasts until something causes you to leave the real block, the switch statement. –  Guvante Apr 16 '13 at 16:50

I will quote Eric Lippert, which answer is pretty clear on the subject:

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.

So, unless you are more intime with the C# developer team of 1999 than Eric Lippert, you will never know the exact reason!

share|improve this answer
    
Not a downvoter, but this was a comment made on the question yesterday. –  Jesse C. Slicer Apr 17 '13 at 14:28
1  
I see that, but since the commentor doesn't post an answer, I thought it could be a good idea to explicitly create the anwser by quoting the content of the link. And I don't care the downvotes! The OP ask the historical reason, not a "How to do it" answer. –  Cyril Gandon Apr 17 '13 at 14:34

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.