Why does C# type pattern matching use a different variable scoping behavior than traditional switch blocks?

by rory.ap   Last Updated May 15, 2019 15:05 PM

Traditional switch blocks have one scope, so the following throws a compiler error "A local variable or function named 'message' is already defined in this scope":

switch(value)
{
    case 1:
        string message = "Val: 1";
        break;
    case 2:

        string message = "Val: 2";
        break;
}

As Eric Lippert states:

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.

There are other good explanations too, like this one:

I think a good reason is that in every other case, the scope of a “normal” local variable is a block delimited by braces ({}).

So then why does scoping behave differently with a type pattern matching switch block?

Animal p = new Dog();

switch(p)
{
    case Dog a:
        break;
    case Cat a: // Why is this legal?           
        break;
}


Answers 2


Seems to be covered in the documentation :

Without pattern matching, this code might be written as follows. The use of type pattern matching produces more compact, readable code by eliminating the need to test whether the result of a conversion is a null or to perform repeated casts.

With pattern matching:

...
    case Array arr:
       Console.WriteLine($"An array with {arr.Length} elements.");
       break;
    case IEnumerable<int> ieInt:
       Console.WriteLine($"Average: {ieInt.Average(s => s)}");
       break;   
...

Without:

...
        if (coll is Array) {
           Array arr = (Array) coll;
           Console.WriteLine($"An array with {arr.Length} elements.");
        }
        else if (coll is IEnumerable<int>) {
            IEnumerable<int> ieInt = (IEnumerable<int>) coll;
            Console.WriteLine($"Average: {ieInt.Average(s => s)}");
        }
...

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch

The switch statement is a bit of a problem child in terms of code design. You should try never to have to use it. But occasionally it can be much clearer than the alternative if blocks

Ewan
Ewan
May 15, 2019 14:52 PM

The short answer is because a is a pattern variable and pattern variables are scoped to their containing block.

For example, if you proceed that switch with if (p is Dog a) return; then it'll no longer compile as it will complain that your two a variables are already defined. That's because the "containing block" for an if is the block that contains the if. However, for a pattern variable in a case label, the containing block is the case block. So in your example, those two a variables exist in separate blocks.

See Scope of pattern variables in the C# 7 docs for details.

David Arno
David Arno
May 15, 2019 14:54 PM

Related Questions



Break on default case in switch

Updated December 18, 2016 08:02 AM

fall-through switch for executing a sequence of steps

Updated November 21, 2018 14:05 PM