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

91

Учитывая следующий код:

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оценивается как ложное. IntelliSense в VS Code сообщает мне, что oэто stringтоже. есть идеи?


Аналогично: C # 7 switch case с нулевыми проверками

Буди
источник
9
Подтверждено. Мне нравится этот вопрос, особенно с учетом наблюдения, oкоторое string(подтверждено с помощью дженериков - т.е. Foo(o)где Foo<T>(T template) => typeof(T).Name) - это очень интересный случай, когда string xведет себя иначе, чем var xдаже когда xон набирается (компилятором) какstring
Марк Грейвелл
7
Случай по умолчанию - мертвый код. Полагаю, мы должны сделать предупреждение там. Проверка.
JaredPar
13
Мне странно, что дизайнеры C # решили varвообще разрешить в этом контексте. Это похоже на то, что я нашел бы в C ++, а не на языке, который, как предполагается, ведет программиста «в пропасть успеха». Здесь varнеоднозначно и бесполезно, то, чего дизайн C # обычно старается избегать.
Питер Дунихо
1
@PeterDuniho Я бы не сказал, что это бесполезно; входящее выражение в switchможет быть непроизносимым - анонимные типы и т. д .; и это не двусмысленно - компилятор явно знает тип; это просто сбивает с толку (по крайней мере, меня), что nullправила такие разные!
Marc Gravell
1
Интересный факт @PeterDuniho - однажды мы искали формальные правила определенного присваивания из спецификации C # 1.2, и в иллюстративном коде расширения было объявление переменной внутри блока (где оно сейчас); он только вышел наружу в версии 2.0, а затем снова вернулся внутрь, когда проблема захвата стала очевидной.
Marc Gravell

Ответы:

69

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

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

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

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

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

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

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

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

ДжаредПар
источник
4
Однако настоящая проблема заключается в том, что компилятор «показывает» принадлежность varк типу, stringхотя на самом деле это не так (честно говоря, не уверен, какой тип должен быть по общему признанию)
shmuelie
@shmuelie рассчитан на тип varв примере string.
JaredPar
5
@JaredPar спасибо за идеи здесь; Лично я бы поддержал большее количество предупреждений, даже если раньше этого не было, но я понимаю ограничения языковой группы. Вы когда-нибудь задумывались о том, чтобы «режим хвастовства обо всем» (возможно, включен по умолчанию) или «устаревший стоический режим» (факультативно)? возможноcsc /stiffUpperLip
Marc Gravell
3
@MarcGravell у нас есть функция, называемая волнами предупреждений, которая призвана упростить и упростить внедрение новых предупреждений. По сути, каждый выпуск компилятора - это новая волна, и вы можете включить предупреждения через / wave: 1, / wave: 2, / wave: all.
JaredPar
4
@JonathanDickinson Я не думаю, что это показывает то, что вы думаете. Это просто показывает, что nullэто действительная stringссылка, и любая stringссылка (включая null) может быть неявно приведена (с сохранением ссылок) к objectссылке, и любая objectссылка, которая есть, nullможет быть успешно преобразована (явным образом) в любой другой тип, все еще существующий null. Не совсем то же самое с точки зрения системы типов компилятора.
Марк Грейвелл
22

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

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; ...- нет nullтеста, несмотря на то, что он выглядит похожим на поверхности, только с компилятором, предоставляющим тип.

наконец-то:

default:

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

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

Марк Гравелл
источник
14

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

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

пользователь541686
источник