cambiar con var / null comportamiento extraño


91

Dado el siguiente código:

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

¿Por qué la declaración del interruptor coincide case var o?

Tengo entendido que case string sno coincide cuando s == nullporque (efectivamente) se (null as string) != nullevalúa como falso. IntelliSense en VS Code me dice que oes unstring . ¿Alguna idea?


Similar a: caso de conmutador C # 7 con comprobaciones nulas


9
Confirmado. Me encanta esta pregunta, especialmente con la observación de que ose string(confirmado con los genéricos - es decir Foo(o), donde Foo<T>(T template) => typeof(T).Name) - se trata de un caso muy interesante en el que string xse comporta de manera diferente que var xincluso cuando xse escribe (por el compilador) comostring
Marc Gravell

7
El caso predeterminado es el código muerto. Cree que deberíamos emitir una advertencia allí. Comprobación.
JaredPar

13
Me resulta extraño que los diseñadores de C # decidieran permitirlo varen este contexto. Seguro que parece el tipo de cosas que encontraría en C ++, no en un lenguaje que pretende llevar al programador "al pozo del éxito". Aquí, vares ambiguo e inútil, cosas que el diseño de C # normalmente parece esforzarse por evitar.
Peter Duniho

1
@PeterDuniho No diría inútil; la expresión entrante al switchpodría ser impronunciable - tipos anónimos, etc; y no es ambiguo : el compilador conoce claramente el tipo; ¡Es confuso (al menos para mí) que las nullreglas sean tan diferentes!
Marc Gravell

1
@PeterDuniho Dato curioso: una vez buscamos las reglas formales de asignación definitiva de la especificación C # 1.2, y el código de expansión ilustrativo tenía la declaración de variable dentro del bloque (donde está ahora); solo se movió hacia afuera en 2.0, luego regresó al interior nuevamente cuando el problema de captura era obvio.
Marc Gravell

Respuestas:


69

Dentro de una switchdeclaración de coincidencia de patrones que usa casepara un tipo explícito, se pregunta si el valor en cuestión es de ese tipo específico o de un tipo derivado. Es el equivalente exacto deis

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

El valor nullno tiene un tipo y, por lo tanto, no cumple ninguna de las condiciones anteriores. El tipo estático de someStringno entra en juego en ningún ejemplo.

El vartipo aunque en la coincidencia de patrones actúa como un comodín y coincidirá con cualquier valor incluyendo null.

El defaultcaso aquí es un código muerto. El case var ocoincidirá con cualquier valor, nulo o no nulo. Un caso no predeterminado siempre gana sobre uno predeterminado, por defaultlo que nunca se alcanzará. Si miras el IL, verás que ni siquiera se emite.

De un vistazo, puede parecer extraño que esto se compile sin ninguna advertencia (definitivamente me desconcertó). Pero esto coincide con el comportamiento de C # que se remonta a 1.0. El compilador admite defaultcasos incluso cuando puede demostrar trivialmente que nunca se alcanzará. Considere como ejemplo lo siguiente:

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

Aquí defaultnunca se activará (incluso si booltiene un valor que no sea 1 o 0). Sin embargo, C # ha permitido esto desde la versión 1.0 sin previo aviso. La coincidencia de patrones simplemente se ajusta a este comportamiento aquí.


4
Sin embargo, el problema real es que el compilador "muestra" varque es del tipo stringcuando en realidad no lo es (honestamente, no estoy seguro de cuál debería ser el tipo)
shmuelie

@shmuelie se calcula que el tipo de varen el ejemplo es string.
JaredPar

5
@JaredPar gracias por las ideas aquí; personalmente, apoyaría la emisión de más advertencias incluso cuando no lo hice anteriormente, pero entiendo las limitaciones del equipo de idiomas. ¿Alguna vez ha considerado un "modo quejido por todo" (posiblemente activado de forma predeterminada) frente al "modo estoico heredado" (electivo)? quizáscsc /stiffUpperLip
Marc Gravell

3
@MarcGravell tenemos una función llamada ondas de advertencia que está destinada a facilitar la introducción de nuevas advertencias, con menos interrupciones de compatibilidad. Básicamente, cada versión del compilador es una nueva ola y puede optar por las advertencias a través de / wave: 1, / wave: 2, / wave: all.
JaredPar

4
@JonathanDickinson No creo que eso muestre lo que tú crees que muestra. Eso solo muestra que una nulles una stringreferencia válida , y cualquier stringreferencia (incluida null) se puede convertir implícitamente (preservando la referencia) a una objectreferencia, y cualquier objectreferencia que se nullpuede convertir con éxito (explícita) a cualquier otro tipo, aún siendo null. No es realmente lo mismo en términos del sistema de tipos de compilador.
Marc Gravell

22

Estoy reuniendo varios comentarios de Twitter aquí; esto es realmente nuevo para mí, y espero que Jaredpar intervenga con una respuesta más completa, pero; versión corta como yo lo entiendo:

case string s:

se interpreta como if(someString is string) { s = (string)someString; ...o if((s = (someString as string)) != null) { ... }(cualquiera de los cuales implica una nullprueba) que ha fallado en su caso; Por el contrario:

case var o:

donde se resuelve el compilador oque stringes simplemente o = (string)someString; ...- no hay nullprueba, a pesar del hecho de que sea similar en la superficie, sólo con el compilador proporcionar el tipo.

finalmente:

default:

aquí no se puede llegar , porque el caso anterior lo atrapa todo. Esto puede ser un error del compilador porque no emitió una advertencia de código inalcanzable.

Estoy de acuerdo en que esto es muy sutil, matizado y confuso. Pero aparentemente el case var oescenario tiene usos con propagación nula ( o?.Length ?? 0etc.). Estoy de acuerdo en que es extraño que esto funcione de manera tan diferente entre var oy string s, pero es lo que hace actualmente el compilador.


14

Es porque case <Type>coincide con el tipo dinámico (tiempo de ejecución), no el tipo estático (tiempo de compilación). nullno tiene un tipo dinámico, por lo que no puede coincidir con string. vares solo la alternativa.

(Publicando porque me gustan las respuestas cortas).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.