Переключатель с странным поведением var/null

С учетом следующего кода:

string someString = null;
switch (someString)
{
    case string s:
        Console.WriteLine("string s");
        break;
    case var o:
        Console.WriteLine("var o");
        break;
    default:
        Console.WriteLine("default");
        break;
}

Почему оператор switch соответствует case var o?

Насколько я понимаю, case string s не соответствует, когда s == null, потому что (эффективно) (null as string) != null оценивается как false. IntelliSense на VS Code говорит мне, что o является string. Любые мысли?


Символьный: С# 7 с короткими ошибками

Ответ 1

Внутри сопоставления с образцом switch с использованием case для явного типа задается вопрос о том, относится ли это значение к определенному типу или производному типу. Это точный эквивалент is

switch (someString) {
  case string s:
}
if (someString is string) 

Значение null не имеет типа и, следовательно, не удовлетворяет ни одному из указанных выше условий. Статический тип someString не входит в игру в любом примере.

Тип var, хотя в сочетании с шаблоном действует как дикая карта и будет соответствовать любому значению, включая null.

Здесь default - мертвый код. case var o будет соответствовать любому значению, null или non-null. Случай без деления всегда выигрывает по умолчанию, поэтому default никогда не будет ударяться. Если вы посмотрите на IL, вы увидите, что он даже не выпущен.

На первый взгляд может показаться странным, что это компиляция без предупреждения (определенно отбросила меня). Но это соответствует поведению С#, которое восходит к 1.0. Компилятор допускает случаи default, даже если он может тривиально доказать, что он никогда не пострадает. Рассмотрим в качестве примера следующее:

bool b = ...;
switch (b) {
  case true: ...
  case false: ...
  default: ...
}

Здесь default никогда не пострадает (даже для bool, у которого есть значение, которое не равно 1 или 0). Тем не менее С# разрешил это с 1.0 без предупреждения. Согласование шаблонов просто соответствует этому поведению.

Ответ 2

Я собираю здесь несколько твиттер-комментариев - это на самом деле для меня ново, и я надеюсь, что Джаредпар скачет с более полным ответом, но; как я понимаю:

case string s:

интерпретируется как if(someString is string) { s = (string)someString; ... или if((s = (someString as string)) != null) { ... } - любой из них включает в себя тест null - который не выполнен в вашем случае; наоборот:

case var o:

где компилятор разрешает o как string просто o = (string)someString; ... - no null test, несмотря на тот факт, что он похож на поверхность, как раз с компилятором, предоставляющим тип.

наконец:

default:

здесь невозможно достичь, потому что вышеприведенный случай ловит все. Это может быть ошибкой компилятора в том, что он не выдавал предупреждения недостижимого кода.

Я согласен, что это очень тонко и тонко, и смущает. Но, по-видимому, сценарий case var o использует с нулевым распространением (o?.Length ?? 0 и т.д.). Я согласен с тем, что странно, что это работает по-разному между var o и string s, но это то, что делает компилятор в настоящее время.

Ответ 3

Это потому, что case <Type> соответствует типу динамический (run-time), а не статическому (время компиляции). null не имеет динамического типа, поэтому он не может сравниться с string. var - это просто резерв.

(Проводка, потому что мне нравятся короткие ответы.)