46

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?

8
  • 5
    Declare your action variable before the switch statement, or put each case into its own braces, and you will get sensible behavior. Commented Apr 15, 2013 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
    Commented Apr 15, 2013 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
    Commented Apr 15, 2013 at 23:47
  • 13
    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." Commented Apr 16, 2013 at 0:15
  • 2
    @AnthonyPegram Yeah, so it would seem they copied the switch semantics from C / Java directly, and later made the decision to make case blocks more idiot proof by requiring jumps at the end, but they simply didn't feel the need to go one step further in idiot-proofing the identifier scope as well.
    – p.s.w.g
    Commented Apr 16, 2013 at 17:10

5 Answers 5

28

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.)

4
  • 1
    +1 Can you provide a link to the scope you're quoting?
    – p.s.w.g
    Commented Apr 16, 2013 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
    Commented Apr 16, 2013 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
    Commented Apr 16, 2013 at 17:13
  • 3
    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. Commented May 2, 2013 at 0:08
48

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.");
}
2
  • Yup used this and it works as described
    – Mvision
    Commented Apr 16, 2013 at 5:54
  • 2
    +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
    Commented Apr 17, 2013 at 15:40
11

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!

2
  • Not a downvoter, but this was a comment made on the question yesterday. Commented Apr 17, 2013 at 14:28
  • 5
    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. Commented Apr 17, 2013 at 14:34
6

The explanation is simple - it is because it is like that in C. Languages like C++, Java and C# have copied the syntax and scoping of the switch statement for the sake of familiarity.

(As stated in another answer, the developers of C# does not have documentation about how this decision regarding the case-scopes was made. But the unstated principle for C# syntax was that unless they had compelling reasons to do it differently, they copied Java.)

In C, the case statements are similar to goto-labels. The switch statement is really a nicer syntax for a computed goto. The cases define the entry points into the switch block. By default the rest of the code will be executed, unless there is an explicit exit. So it only makes sense they use the same scope.

(More fundamentally. Goto's are not structured - they do not define or delimit sections of code, they only defines jump-points. Therefore a goto label cannot introduce a scope.)

C# retains the syntax, but introduces a safeguard against "fall through" by requiring an exit after each (non-empty) case clause. But this changes how we think of the switch! The cases are now alternate branches, like the branches in an if-else. This means we would expect each branch to defines its own scope just like if-clauses or iteration clauses.

So in short: Cases share the same scope because this is how it is in C. But it seems weird and inconsistent in C# because we think of cases as alternate branches rather than goto targets.

1
  • 2
    "it seems weird and inconsistent in C# because we think of cases as alternate branches rather than goto targets" That's precisely the confusion I had. +1 for explaining the aesthetic reasons behind why it feels wrong in C#.
    – p.s.w.g
    Commented Oct 6, 2017 at 16:46
4

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.

4
  • 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
    Commented Apr 15, 2013 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
    Commented Apr 16, 2013 at 0:26
  • 1
    Then why couldn't you count case instead?
    – svick
    Commented Apr 16, 2013 at 0:28
  • 2
    @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
    Commented Apr 16, 2013 at 16:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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