C# Language Design – Why Doesn’t C# Have Local Scope in Case Blocks?

clanguage-design

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?

Best Answer

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

Related Topic