mudar com comportamento estranho var / null


91

Dado o seguinte 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 que a instrução switch está ativada case var o?

É meu entendimento que case string snão corresponde a quando s == nullporque (efetivamente) (null as string) != nullavalia como falso. O IntelliSense no VS Code me diz que também oé string. Alguma ideia?


Semelhante a: caso de switch C # 7 com verificações nulas


9
Confirmado. Eu amo essa pergunta, especialmente com a observação de que oé string(confirmado com genéricos - ou seja, Foo(o)onde Foo<T>(T template) => typeof(T).Name) - é um caso muito interessante onde string xse comporta de forma diferente do que var xmesmo quando xé digitado (pelo compilador) comostring
Marc Gravell

7
O caso padrão é o código morto. Acredite que devemos emitir um aviso aí. Verificando.
JaredPar

13
É estranho para mim que os designers do C # tenham decidido permitir varesse contexto. Isso com certeza parece o tipo de coisa que eu encontraria em C ++, não em uma linguagem que pretende levar o programador "ao poço do sucesso". Aqui, varé ambíguo e inútil, coisas que o design C # normalmente parece se esforçar para evitar.
Peter Duniho

1
@PeterDuniho Eu não diria inútil; a expressão de entrada para o switchpode ser impronunciável - tipos anônimos, etc; e não é ambíguo - o compilador conhece claramente o tipo; é apenas confuso (pelo menos para mim) que as nullregras sejam tão diferentes!
Marc Gravell

1
@PeterDuniho fato engraçado - uma vez nós procuramos as regras formais de atribuição definitiva da especificação C # 1.2, e o código de expansão ilustrativo tinha a declaração de variável dentro do bloco (onde está agora); ele só foi movido para fora no 2.0 e depois de volta para dentro quando o problema de captura era óbvio.
Marc Gravell

Respostas:


69

Dentro de uma switchinstrução de correspondência de padrões usando um casepara um tipo explícito está perguntando se o valor em questão é daquele tipo específico ou um tipo derivado. É o equivalente exato deis

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

O valor nullnão tem um tipo e, portanto, não satisfaz nenhuma das condições acima. O tipo estático de someStringnão entra em jogo em nenhum dos exemplos.

O vartipo embora na correspondência de padrões atua como um curinga e corresponderá a qualquer valor incluindo null.

O defaultcaso aqui é um código morto. O case var ocorresponderá a qualquer valor, nulo ou não nulo. Um caso não padrão sempre vence o padrão, portanto default, nunca será atingido. Se você olhar para o IL, verá que nem mesmo é emitido.

À primeira vista, pode parecer estranho que isso compile sem qualquer aviso (definitivamente me confundiu). Mas isso é compatível com o comportamento do C # que remonta a 1.0. O compilador permite defaultcasos mesmo quando pode provar trivialmente que nunca será atingido. Considere como exemplo o seguinte:

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

Aqui defaultnunca será atingido (mesmo boolque tenha um valor que não seja 1 ou 0). No entanto, o C # permite isso desde 1.0 sem aviso. A correspondência de padrões está apenas se alinhando com esse comportamento aqui.


4
O verdadeiro problema, porém, é que o compilador "mostra" varser do tipo stringquando realmente não é (honestamente, não tenho certeza de qual tipo deve ser admitido)
shmuelie

@shmuelie o tipo de varno exemplo é calculado para ser string.
JaredPar

5
@JaredPar obrigado pelos insights aqui; pessoalmente, eu apoiaria mais emissão de avisos, mesmo quando não o fazia anteriormente, mas eu entendo as restrições da equipe de linguagem. Você já considerou um "modo de reclamação sobre tudo" (possivelmente ativado por padrão) em vez do "modo estóico legado" (eletivo)? talvezcsc /stiffUpperLip
Marc Gravell

3
@MarcGravell, temos um recurso chamado ondas de aviso que visa tornar mais fácil e menos compatível com a introdução de novos avisos. Essencialmente, cada versão do compilador é uma nova onda e você pode optar pelos avisos via / wave: 1, / wave: 2, / wave: all.
JaredPar

4
@JonathanDickinson Não acho que isso mostra o que você acha que mostra. Isso apenas mostra que a nullé uma stringreferência válida , e qualquer stringreferência (incluindo null) pode ser convertida implicitamente (preservação de referência) para uma objectreferência e qualquer objectreferência que nullpode ser convertida (explícita) com êxito para qualquer outro tipo, ainda sendo null. Não é realmente a mesma coisa em termos de sistema de tipo de compilador.
Marc Gravell

22

Estou juntando vários comentários do Twitter aqui - isso é realmente novo para mim, e espero que jaredpar me dê uma resposta mais abrangente, mas; versão curta como eu entendo:

case string s:

é interpretado como if(someString is string) { s = (string)someString; ...ou if((s = (someString as string)) != null) { ... }- qualquer um dos quais envolve um nullteste - que falhou no seu caso; inversamente:

case var o:

onde o compilador resolve ocomo stringestá simplesmente o = (string)someString; ...- nenhum nullteste, apesar do fato de que parece semelhante na superfície, apenas com o compilador fornecendo o tipo.

finalmente:

default:

aqui não pode ser alcançado , porque o caso acima abrange tudo. Isso pode ser um bug do compilador, pois ele não emitiu um aviso de código inacessível.

Concordo que isso é muito sutil, matizado e confuso. Mas aparentemente o case var ocenário tem usos com propagação nula ( o?.Length ?? 0etc). Concordo que é estranho que isso funcione de maneira muito diferente entre var oe string s, mas é o que o compilador faz atualmente.


14

É porque case <Type>corresponde ao tipo dinâmico (tempo de execução), não ao tipo estático (tempo de compilação). nullnão tem um tipo dinâmico, por isso não pode ser comparado string. varé apenas o retrocesso.

(Postando porque gosto de respostas curtas.)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.