mit var / null seltsames Verhalten wechseln

91

Gegeben den folgenden Code:

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;
}

Warum stimmt die switch-Anweisung überein case var o?

Es ist mein Verständnis, case string sdas nicht übereinstimmt, wenn, s == nullweil (effektiv) (null as string) != nullals falsch bewertet. IntelliSense auf VS Code sagt mir, dass dies auch ein oist string. Irgendwelche Gedanken?


Ähnlich wie: C # 7-Schalterfall mit Nullprüfungen

budi
quelle
9
Bestätigt. Ich liebe diese Frage, vor allem mit der Beobachtung , dass osie string(mit Generika bestätigt - dh Foo(o)wo Foo<T>(T template) => typeof(T).Name) - es ist ein sehr interessanter Fall, string xverhält sich anders als var xselbst wenn xeingegeben wird (durch den Compiler) alsstring
Marc GRA
7
Der Standardfall ist toter Code. Glauben Sie, wir sollten dort eine Warnung herausgeben. Überprüfung.
JaredPar
13
Es ist seltsam für mich, dass die C # -Designer beschlossen haben, dies varin diesem Zusammenhang überhaupt zuzulassen . Das scheint sicher die Art von Dingen zu sein, die ich in C ++ finden würde, nicht in einer Sprache, die den Programmierer "in die Grube des Erfolgs" führen soll. Hier varist sowohl mehrdeutig als auch nutzlos, Dinge, die C # -Design normalerweise zu vermeiden scheint.
Peter Duniho
1
@PeterDuniho Ich würde nicht sagen, nutzlos; Der eingehende Ausdruck für switchkönnte unaussprechlich sein - anonyme Typen usw.; und es ist nicht mehrdeutig - der Compiler kennt den Typ genau; Es ist nur verwirrend (zumindest für mich), dass die nullRegeln so unterschiedlich sind!
Marc Gravell
1
@PeterDuniho lustige Tatsache - wir haben einmal die definitiven formalen Zuweisungsregeln aus der C # 1.2-Spezifikation nachgeschlagen, und der illustrative Erweiterungscode hatte die Variablendeklaration innerhalb des Blocks (wo sie jetzt ist); es bewegte sich erst in 2.0 nach außen und dann wieder nach innen, als das Erfassungsproblem offensichtlich war.
Marc Gravell

Antworten:

69

In einer Mustervergleichsanweisung switchmit a casefür einen expliziten Typ wird gefragt, ob der betreffende Wert von diesem bestimmten Typ oder einem abgeleiteten Typ ist. Es ist das genaue Äquivalent vonis

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

Der Wert nullhat keinen Typ und erfüllt daher keine der oben genannten Bedingungen. Der statische Typ von someStringkommt in beiden Beispielen nicht ins Spiel.

Der varTyp beim Mustervergleich fungiert jedoch als Platzhalter und entspricht jedem Wert, einschließlich null.

Der defaultFall hier ist toter Code. Das entspricht case var ojedem Wert, null oder nicht null. Ein nicht standardmäßiger Fall gewinnt immer über einen standardmäßigen Fall und defaultwird daher niemals getroffen. Wenn Sie sich die IL ansehen, werden Sie sehen, dass sie nicht einmal ausgestrahlt wird.

Auf den ersten Blick mag es seltsam erscheinen, dass dies ohne Vorwarnung kompiliert wird (hat mich definitiv abgeworfen). Dies stimmt jedoch mit dem C # -Verhalten überein, das auf 1.0 zurückgeht. Der Compiler erlaubt defaultFälle, auch wenn er trivial beweisen kann, dass er niemals getroffen wird. Betrachten Sie als Beispiel Folgendes:

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

Hier defaultwird niemals getroffen (auch boolwenn der Wert nicht 1 oder 0 ist). C # hat dies jedoch seit 1.0 ohne Vorwarnung zugelassen. Der Mustervergleich entspricht hier nur diesem Verhalten.

JaredPar
quelle
4
Das eigentliche Problem ist jedoch, dass der Compiler "zeigt" var, dass er vom Typ ist, stringwenn er wirklich nicht ist (ehrlich gesagt nicht sicher, welcher Typ zugegebenermaßen sein soll)
shmuelie
@shmuelie der Typ von varim Beispiel wird berechnet string.
JaredPar
5
@JaredPar danke für die Einblicke hier; persönlich würde ich mehr Warnungen unterstützen, auch wenn dies vorher nicht der Fall war, aber ich verstehe die Einschränkungen des Sprachteams. Haben Sie jemals über einen "Whinge About Everything" -Modus (möglicherweise standardmäßig aktiviert) im Vergleich zum "Legacy Stoic Mode" (Wahlfach) nachgedacht? vielleichtcsc /stiffUpperLip
Marc Gravell
3
@MarcGravell Wir haben eine Funktion namens Warnwellen, die es einfacher und weniger kompatibel machen soll, neue Warnungen einzuführen. Im Wesentlichen ist jede Compiler-Version eine neue Welle, und Sie können die Warnungen über / wave: 1, / wave: 2, / wave: all aktivieren.
JaredPar
4
@ JonathanDickinson Ich glaube nicht, dass das zeigt, was du denkst, dass es zeigt. Das zeigt nur, dass a nulleine gültige stringReferenz ist, und jede stringReferenz (einschließlich null) kann implizit in eine objectReferenz umgewandelt (referenzerhaltend) werden , und jede objectReferenz, nulldie erfolgreich auf einen anderen Typ übertragen werden kann (explizit), ist noch vorhanden null. Nicht wirklich dasselbe in Bezug auf das Compilertypsystem.
Marc Gravell
22

Ich stelle hier mehrere Twitter-Kommentare zusammen - das ist eigentlich neu für mich und ich hoffe, dass Jaredpar mit einer umfassenderen Antwort einspringt, aber; Kurzfassung, wie ich sie verstehe:

case string s:

wird interpretiert als if(someString is string) { s = (string)someString; ...oder if((s = (someString as string)) != null) { ... }- was einen nullTest beinhaltet - was in Ihrem Fall fehlgeschlagen ist; umgekehrt:

case var o:

wo die Compiler Entschlüsse oals stringeinfach ist o = (string)someString; ...- kein nullTest, trotz der Tatsache , dass es auf der Oberfläche ähnlich sieht, nur mit dem Compiler die Art zu schaffen.

endlich:

default:

hier kann nicht erreicht werden , weil der obige Fall alles fängt. Dies kann ein Compiler-Fehler sein, da keine nicht erreichbare Code-Warnung ausgegeben wurde.

Ich stimme zu, dass dies sehr subtil und nuanciert und verwirrend ist. Aber anscheinend hat das case var oSzenario Verwendung mit Null-Weitergabe ( o?.Length ?? 0usw.). Ich bin damit einverstanden , dass es seltsam ist , dass das funktioniert so sehr unterschiedlich zwischen var ound string s, aber es ist das, was der Compiler zur Zeit der Fall ist.

Marc Gravell
quelle
14

Dies liegt daran, dass case <Type>Übereinstimmungen mit dem dynamischen Typ (Laufzeit) und nicht mit dem statischen Typ (Kompilierungszeit) übereinstimmen. nullhat keinen dynamischen Typ, kann also nicht mithalten string. varist nur der Fallback.

(Posting, weil ich kurze Antworten mag.)

user541686
quelle