switch con var / null comportamento strano


91

Dato il seguente codice:

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

Perché la corrispondenza dell'istruzione switch è attiva case var o?

È mia comprensione che case string snon corrisponde quando s == nullperché (effettivamente) (null as string) != nullrestituisce falso. IntelliSense su VS Code mi dice che oè anche un file string. qualche idea?


Simile a: C # 7 switch case con controlli null


9
Confermato. Adoro questa domanda, soprattutto con l'osservazione che oè string(confermata con i generici - cioè Foo(o)dove Foo<T>(T template) => typeof(T).Name) - è un caso molto interessante in cui string xsi comporta in modo diverso var xanche quando xviene digitato (dal compilatore) comestring
Marc Gravell

7
Il caso predefinito è codice morto. Credo che dovremmo lanciare un avvertimento lì. Controllo.
JaredPar

13
È strano per me che i progettisti di C # abbiano deciso di consentire varin questo contesto. Sembra proprio il genere di cose che troverei in C ++, non in un linguaggio che si pretende portare il programmatore "nella fossa del successo". Qui, varè ambiguo e inutile, cose che la progettazione C # sembra in genere cercare di evitare.
Peter Duniho

1
@PeterDuniho non direi inutile; l'espressione in entrata al fileswitch potrebbe essere impronunciabile - tipi anonimi, ecc; e non è ambiguo : il compilatore conosce chiaramente il tipo; è solo fonte di confusione (almeno per me) che le nullregole siano così diverse!
Marc Gravell

1
@PeterDuniho curiosità: una volta abbiamo cercato le regole formali di assegnazione definite dalla specifica C # 1.2 e il codice di espansione illustrativo aveva la dichiarazione della variabile all'interno del blocco (dove si trova ora); si spostava solo all'esterno nella 2.0, quindi di nuovo all'interno quando il problema di acquisizione era evidente.
Marc Gravell

Risposte:


69

All'interno di switchun'istruzione di corrispondenza del modello che utilizza a caseper un tipo esplicito si chiede se il valore in questione è di quel tipo specifico o di un tipo derivato. È l'esatto equivalente diis

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

Il valore nullnon ha un tipo e quindi non soddisfa nessuna delle condizioni precedenti. Il tipo statico di someStringnon entra in gioco in nessuno dei due esempi.

Il vartipo anche se nella corrispondenza del modello agisce come un carattere jolly e corrisponderà a qualsiasi valore incluso null.

Il defaultcaso qui è codice morto. Il case var ocorrisponderà qualsiasi valore, null o non null. Un caso non predefinito vince sempre su uno predefinito, quindi defaultnon verrà mai colpito. Se guardi l'IL vedrai che non viene nemmeno emesso.

A prima vista può sembrare strano che questo si compili senza alcun preavviso (decisamente mi ha sbalordito). Ma questo corrisponde al comportamento di C # che risale a 1.0. Il compilatore consente defaultcasi anche quando può provare banalmente che non verrà mai colpito. Considera come esempio quanto segue:

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

Qui defaultnon verrà mai colpito (nemmeno perbool se ha un valore diverso da 1 o 0). Tuttavia C # lo ha consentito dalla 1.0 senza preavviso. Il pattern matching è in linea con questo comportamento qui.


4
Il vero problema però è che il compilatore "mostra" vardi essere di tipo stringquando in realtà non lo è (sinceramente non sono sicuro di quale dovrebbe essere il tipo, ammettiamolo)
shmuelie

@shmuelie il tipo di varnell'esempio è calcolato essere string.
JaredPar

5
@ JaredPar grazie per gli approfondimenti qui; personalmente sosterrei l'emissione di più avvisi anche quando non lo faceva in precedenza, ma capisco i vincoli del team linguistico. Hai mai considerato una "modalità piagnucolona su tutto" (possibilmente attivata per impostazione predefinita), rispetto alla "modalità stoica legacy" (elettiva)? forsecsc /stiffUpperLip
Marc Gravell

3
@ MarcGravell abbiamo una funzione chiamata warning waves che ha lo scopo di rendere più facile, meno compatibile, introdurre nuovi avvisi. Essenzialmente ogni versione del compilatore è una nuova ondata e puoi attivare gli avvisi tramite / wave: 1, / wave: 2, / wave: all.
JaredPar

4
@JonathanDickinson Non penso che mostri quello che pensi che mostri. Ciò mostra solo che a nullè un stringriferimento valido e qualsiasi stringriferimento (incluso null) può essere cast implicitamente (preservando il riferimento) a un objectriferimento, e qualsiasi objectriferimento che è nullpuò essere trasmesso con successo (esplicito) a qualsiasi altro tipo, ancora in essere null. Non proprio la stessa cosa in termini di sistema del tipo di compilatore.
Marc Gravell

22

Sto mettendo insieme più commenti su Twitter qui - questo in realtà è nuovo per me, e spero che jaredpar salti dentro con una risposta più completa, ma; versione breve a quanto ho capito:

case string s:

è interpretato come if(someString is string) { s = (string)someString; ...o if((s = (someString as string)) != null) { ... }- uno dei quali implica un nulltest - che è fallito nel tuo caso; al contrario:

case var o:

dove il compilatore si risolve ocome stringè semplicemente o = (string)someString; ...- nessun nulltest, nonostante il fatto che sia simile in superficie, solo con il compilatore che fornisce il tipo.

finalmente:

default:

qui non può essere raggiunto , perché il caso sopra cattura tutto. Potrebbe trattarsi di un bug del compilatore in quanto non ha emesso un avviso di codice non raggiungibile.

Sono d'accordo che questo è molto sottile e sfumato e confuso. Ma a quanto pare lo case var oscenario ha usi con propagazione nulla ( o?.Length ?? 0ecc.). Sono d'accordo sul fatto che sia strano che funzioni in modo molto diverso tra var oe string s, ma è ciò che fa attualmente il compilatore.


14

È perché case <Type>corrisponde al tipo dinamico (in fase di esecuzione), non al tipo statico (in fase di compilazione). nullnon ha un tipo dinamico, quindi non può corrispondere a string. varè solo il fallback.

(Pubblicare perché mi piacciono le risposte brevi.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.