Biorąc pod uwagę następujący kod:
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;
}
Dlaczego instrukcja switch jest zgodna case var o
?
Rozumiem, że case string s
nie pasuje, kiedy, s == null
ponieważ (skutecznie) (null as string) != null
ocenia jako fałsz. IntelliSense w programie VS Code mówi mi, że o
to string
również. jakieś pomysły?
Podobny do: Przypadek przełącznika C # 7 z weryfikacją wartości null
o
jeststring
(potwierdzone z rodzajowych - czyliFoo(o)
gdzieFoo<T>(T template) => typeof(T).Name
) - jest to bardzo ciekawy przypadek, w którymstring x
zachowuje się inaczej niżvar x
nawet gdyx
jest wpisane (przez kompilator) jakostring
var
w ogóle zdecydowali się na to w tym kontekście. To z pewnością wydaje się być czymś, co znajdę w C ++, a nie w języku, który rzekomo prowadzi programistę „do otchłani sukcesu”. Tutajvar
jest zarówno niejednoznaczne, jak i bezużyteczne, rzeczy, których projekt C # zwykle stara się unikać.switch
może być niewypowiadalne - typy anonimowe itp .; i nie jest niejednoznaczne - kompilator wyraźnie zna typ; to jest po prostu mylące (przynajmniej dla mnie), żenull
zasady są tak różne!Odpowiedzi:
Wewnątrz
switch
instrukcji dopasowywania wzorców używająca acase
dla typu jawnego jest pytaniem, czy dana wartość jest tego konkretnego typu, czy też typu pochodnego. To dokładny odpowiednikis
switch (someString) { case string s: } if (someString is string)
Wartość
null
nie ma typu i dlatego nie spełnia żadnego z powyższych warunków. WsomeString
żadnym z przykładów nie ma znaczenia typ statyczny .var
Typ choć w strukturze dopasowywania działa jako dzikie karty i będzie pasował do żadnej wartości łącznienull
.default
Sprawa tutaj jest martwy kod.case var o
Dopasuje jakąkolwiek wartość, null lub niezerowe. Przypadki inne niż domyślne zawsze wygrywają z przypadkami domyślnymi, więcdefault
nigdy nie zostaną trafione. Jeśli spojrzysz na IL, zobaczysz, że nie jest on nawet emitowany.Na pierwszy rzut oka może się wydawać dziwne, że kompiluje się to bez żadnego ostrzeżenia (zdecydowanie mnie wyrzuciło). Ale to jest zgodne z zachowaniem C #, które sięga wstecz do 1.0. Kompilator dopuszcza
default
przypadki, nawet jeśli może w trywialny sposób udowodnić, że nigdy nie zostanie trafiony. Jako przykład rozważ następujące kwestie:bool b = ...; switch (b) { case true: ... case false: ... default: ... }
Tutaj
default
nigdy nie zostanie trafiony (nawet jeślibool
ma wartość inną niż 1 lub 0). Jednak C # zezwolił na to od 1.0 bez ostrzeżenia. Dopasowywanie wzorców jest tutaj zgodne z tym zachowaniem.źródło
var
jako typ,string
kiedy naprawdę nie jest (szczerze mówiąc nie jestem pewien, jaki powinien być typ)var
jest obliczany jako typ w przykładziestring
.csc /stiffUpperLip
null
jest prawidłowymstring
odwołaniem, a każdestring
odwołanie (w tymnull
) może być niejawnie rzutowane (zachowując referencję) naobject
odwołanie, a każdeobject
odwołanie, którenull
może zostać pomyślnie przesłane (jawne) do dowolnego innego typu, nadal jestnull
. Nie jest to to samo, jeśli chodzi o system typów kompilatora.Łączę tutaj wiele komentarzy na Twitterze - to właściwie dla mnie nowość i mam nadzieję, że jaredpar skoczy z bardziej wyczerpującą odpowiedzią, ale; wersja skrócona, jak rozumiem:
case string s:
jest interpretowany jako
if(someString is string) { s = (string)someString; ...
lubif((s = (someString as string)) != null) { ... }
- z których każdy obejmujenull
test - który w Twoim przypadku nie powiódł się; odwrotnie:case var o:
gdzie postanawia kompilatora
o
jakstring
to po prostuo = (string)someString; ...
- nonull
badanie, pomimo faktu, że wygląda podobnie na powierzchni, tylko z kompilatora zapewniając typ.Wreszcie:
default:
tutaj nie można dotrzeć , ponieważ powyższy przypadek obejmuje wszystko. Może to być błąd kompilatora, ponieważ nie emitował ostrzeżenia o nieosiągalnym kodzie.
Zgadzam się, że jest to bardzo subtelne, dopracowane i zagmatwane. Ale najwyraźniej
case var o
scenariusz ma zastosowania z propagacją zerową (o?.Length ?? 0
itp.). Zgadzam się, że to dziwne, że działa to tak bardzo różnie w przypadkuvar o
istring s
, ale tak właśnie działa kompilator.źródło
Dzieje się tak, ponieważ
case <Type>
pasuje do typu dynamicznego (w czasie wykonywania), a nie do typu statycznego (w czasie kompilacji).null
nie ma typu dynamicznego, więc nie może się z nim równaćstring
.var
to tylko rozwiązanie awaryjne.(Publikuję, ponieważ lubię krótkie odpowiedzi.)
źródło