How is switch variable declaration scoped?

How is following possible?

switch (param.ParameterType)
{
    case Type x when x == typeof(byte):
        int invalid;
        break;
    case Type x when x == typeof(short):
        int invalid;
        break;
    case Type x when x == typeof(int):
        break;
    case Type x when x == typeof(long):
        break;
}

Question is, how is x scoped inside each case without any visible blocks. meanwhile, variable invalid cant be declared in different switch cases. it has to be inside a block.

if there is no block then following scoping of variables would be impossible.

{
    // case byte:
    Type x;
    int invalid;
    // break;

     // case short:
    Type x; // x can not be declared twice.
    int invalid;
}

if there is invisible block for each case, then following must be possible (but its not).

{
    {   // block for each case.
        // case byte:
        Type x;
        int invalid;
        // break;
    }

    {
        // case short:
        Type x;
        int invalid; // but this throws compile time error.
    }
}

It seems that compiler is doing some magic here, apparently x is scoped differently than invalid variable. is this bug inside Semantic analysis phase of compiler?

Jon Skeet
people
quotationmark

Question is, how is x scoped inside each case without any visible blocks. meanwhile, variable invalid cant be declared in different switch cases. it has to be inside a block.

Variables introduced via pattern matching in case labels only have the scope of the body of that case.

Variables introduced "normally" in the case bodies have the scope of the whole switch statement.

Yes, it's inconsistent - but I'd argue that:

  • It's particularly useful to be able to introduce multiple variables with the same name via pattern matching
  • The scoping of variables introduced in case statements was a design mistake to start with, and this is just preventing the mistake from going any further

Note that you can't declare the same variable multiple times using pattern matches for cases which use the same case block. For example, with a simplification of your code, this is fine:

object o = null;
switch (o)
{
    case Type x when x == typeof(byte):
        break;
    case Type x when x == typeof(short):
        break;
}

But this isn't:

object o = null;
switch (o)
{
    case Type x when x == typeof(byte):
    case Type x when x == typeof(short):
        break;
}

Arguably the compiler could have some rules to allow you to introduce multiple variables so long as they're of the same type - that could be really handy for common code. But it would definitely make the language even more complicated...


As an example of the "design mistake" point, the C# 5 specification actually has an error due to it. The C# 5 spec (8.7.2) claims:

The “no fall through” rule prevents a common class of bugs that occur in C and C++ when break statements are accidentally omitted. In addition, because of this rule, the switch sections of a switch statement can be arbitrarily rearranged without affecting the behavior of the statement.

This "arbitrary rearrangement" is untrue in C# 7 due to pattern matching ordering anyway, but it's always been untrue. Consider this code:

class Test
{
    static void Main(string[] args)
    {
        switch (args.Length)
        {
            case 0:
                string x;
                break;
            case 1:
                x = args[0];
                break;
        }
    }
}

That's valid due to the odd scoping rules - x is in scope and usable in the "case 1" block. If you rearrange the cases, however:

class Test
{
    static void Main(string[] args)
    {
        switch (args.Length)
        {
            case 1:
                x = args[0]; // Invalid
                break;
            case 0:
                string x;
                break;
        }
    }
}

... this now gives a compile-time error. The variable is still in scope (the compiler knows what you mean by x) but you can't assign a value to a local variable before its declaration.

As far as I'm aware no-one ever wants to use a variable declared by an earlier scope - it would have made much more sense either for each case block to introduce a new variable declaration space, or for C# to require braces for the case block anyway.

people

See more on this question at Stackoverflow