Depois de ler esta pergunta no HNQ, passei a ler sobre tipos de referência nulos no C # 8 e fiz algumas experiências.
Estou ciente de que 9 vezes em 10, ou ainda mais frequentemente, quando alguém diz "Encontrei um bug do compilador!" isso é realmente por design e por seu próprio mal-entendido. E desde que comecei a examinar esse recurso somente hoje, claramente não tenho um entendimento muito bom dele. Com isso fora do caminho, vamos olhar para este código:
#nullable enable
class Program
{
static void Main()
{
var s = "";
var b = s == null; // If you comment this line out, the warning on the line below disappears
var i = s.Length; // warning CS8602: Dereference of a possibly null reference
}
}
Depois de ler a documentação a que vinculei acima, esperaria que a s == null
linha me desse um aviso - afinal de contas s
é claramente não nulo, portanto, compará-la com null
não faz sentido.
Em vez disso, estou recebendo um aviso na próxima linha, e o aviso diz que s
é possível uma referência nula, mesmo que, para um humano, seja óbvio que não.
Além disso, o aviso não será exibido se não compararmos s
com null
.
Eu pesquisei no Google e encontrei um problema do GitHub , que acabou por ser sobre algo totalmente diferente, mas no processo tive uma conversa com um colaborador que forneceu mais informações sobre esse comportamento (por exemplo, "Verificações nulas geralmente são uma maneira útil de dizer ao compilador para redefinir sua inferência anterior sobre a nulidade de uma variável. " ). Isso ainda me deixou com a pergunta principal sem resposta, no entanto.
Em vez de criar um novo problema no GitHub e potencialmente ocupar o tempo dos colaboradores incrivelmente ocupados do projeto, estou divulgando isso para a comunidade.
Poderia me explicar o que está acontecendo e por quê? Em particular, por que nenhum aviso é gerado na s == null
linha e por que temos CS8602
quando parece que uma null
referência não é possível aqui? Se a inferência de nulidade não é à prova de balas, como sugere o encadeamento do GitHub vinculado, como isso pode dar errado? Quais seriam alguns exemplos disso?
fonte
?
porques
não é anulável. Não se torna anulável, simplesmente porque éramos tolos o suficiente para compará-lonull
.Respostas:
Esta é efetivamente uma duplicata da resposta que o @stuartd vinculou, então não vou entrar em detalhes super profundos aqui. Mas a raiz do problema é que não se trata de um erro de linguagem nem de um compilador, mas seu comportamento pretendido exatamente como implementado. Nós rastreamos o estado nulo de uma variável. Quando você declara inicialmente a variável, esse estado é NotNull porque você a inicializa explicitamente com um valor que não é nulo. Mas não rastreamos de onde esse NotNull veio. Este, por exemplo, é efetivamente um código equivalente:
Em ambos os casos, você testar explicitamente
s
paranull
. Tomamos isso como entrada para a análise de fluxo, assim como Mads respondeu nesta pergunta: https://stackoverflow.com/a/59328672/2672518 . Nessa resposta, o resultado é que você recebe um aviso sobre o retorno. Nesse caso, a resposta é que você recebe um aviso de que não referenciou uma referência possivelmente nula.Sim, ele realmente faz. Para o compilador. Como humanos, podemos olhar para este código e, obviamente, entender que ele não pode lançar uma exceção de referência nula. Mas a maneira como a análise de fluxo anulável é implementada no compilador, não pode. Discutimos algumas melhorias nesta análise, onde adicionamos estados adicionais com base na origem do valor, mas decidimos que isso adicionava uma grande complexidade à implementação, sem muito ganho, porque os únicos lugares onde isso seria útil em casos como este, em que o usuário inicializa uma variável com um
new
valor constante ou constante e a verifica denull
qualquer maneira.fonte
s == null
que não produz um aviso?#nullable enable
;string s = "";s = null;
compila e trabalha (ainda produz um aviso) quais são os benefícios da implementação que permite atribuir nulo a uma "referência não anulável" no contexto de anotação nula ativado?s == null
. Talvez, por exemplo, você esteja em um método público e deseje validar parâmetros. Ou talvez você esteja usando uma biblioteca que anotou incorretamente e, até que consertem esse bug, você precisará lidar com nulo onde não foi declarado. Em qualquer um desses casos, se avisássemos, seria uma experiência ruim. Quanto à permissão de atribuição: as anotações de variáveis locais são apenas para leitura. Eles não afetam o tempo de execução. De fato, colocamos todos esses avisos em um único código de erro, para que você possa desativá-los se desejar reduzir a rotatividade do código.Felizmente, adotei as referências anuláveis do C # 8 assim que disponíveis. Como eu costumava usar a notação [NotNull] (etc.) do ReSharper, notei algumas diferenças entre os dois.
O compilador C # pode ser enganado, mas tende a errar por precaução (geralmente, nem sempre).
Como referência para futuros visitantes, esses são os cenários dos quais vi o compilador bastante confuso (presumo que todos esses casos sejam projetados):
No entanto, permite usar alguma construção para verificar a nulidade que também se livra do aviso:
ou atributos (ainda bem legais) (encontre todos aqui ):
string?
é apenas uma string,int?
torna-se umNullable<int>
e força o compilador a lidar com eles de maneiras substancialmente diferentes. Também aqui, o compilador está escolhendo o caminho seguro, forçando você a especificar o que está esperando:Restrições de doação resolvidas:
Mas se não usarmos restrições e removermos o '?' de Data, ainda podemos inserir valores nulos usando a palavra-chave 'default':
O último parece o mais complicado para mim, pois permite escrever código inseguro.
Espero que isso ajude alguém.
fonte
ThrowIfNull(s);
está me assegurando ques
não é nulo). Além disso, o artigo explica como manipular genéricos não anuláveis , enquanto eu mostrava como você pode "enganar" o compilador, tendo um valor nulo, mas sem aviso.DoesNotReturnIf(bool)
.DoesNotReturnIfNull(nullable)
.