C#8.0 — Tipos de referências anuláveis

Quem nunca, durante a vida de programador, já não se deparou com o erro de referência nula: o tão temido (e muitas vezes irritante) “System.NullReferenceException: ‘Object reference not set to an instance of an object.’”?
Esse erro pode ocorrer por diversos motivos, como quando uma variável do tipo de referência é declarada e é atribuída o valor nulo, e posteriormente no código tentamos acessar alguma propriedade ou método desta variável. E o pior, esse erro ocorre apenas em tempo de execução, e muitas vezes pode ser difícil de ser detectado, principalmente porque é um erro genérico e geralmente não esperado.
Para exemplificar, vamos criar um console application em .NET Core 3.0:
dotnet new console -n NullableReferences
Nesse console application, vamos criar o método estático RetornaTextoNulo que retorna uma string nula, e no método Main vamos definir algumas strings: algumas com valor, outras nulas e outras vazias, e em todas vamos acessar a propriedade Length e atribuir o comprimento da string em uma variável contagem.
Veja que o código está correto no ponto de vista da compilação: você vai conseguir compilar e até fazer a execução do programa. Porém, quando a aplicação é executada nos deparamos com o erro de NullReferenceException quando é feita a tentativa de acessar o Length da string nula!

Em uma aplicação de verdade, em um projeto complexo, mesmo para um desenvolvedor experiente, um erro destes pode não ser tão facilmente detectado, agora imagine em um time com pessoas de vários níveis de conhecimento e de experiência: esse erro pode acabar demorando muito para ser detectado, impactando diretamente no funcionamento da aplicação!
Hoje já existem várias formas de resolver e evitar essa questão, porém todas dependem da iniciativa do desenvolvedor, e quando algo fica dependendo de ação de uma pessoa sempre existe a possibilidade da implementação não ser feita, o erro passar e ser descoberto apenas na execução da aplicação.
E é justamente para tentar diminuir os casos de erro de referência nula que no C# 8.0, a nova versão da linguagem disponibilizada juntamente com o .NET Core 3.0, implementa uma funcionalidade para ajudar na detecção desse tipo de problema.
Nullable reference types
Essa nova funcionalidade do C# 8 tem por objetivo alertar para o desenvolvedor ainda em tempo de desenvolvimento de possível problemas de referência nula, permitindo que o código seja conscientemente criado com tipos de referência que podem ou não receber um valor nulo, da mesma forma que existem variáveis nulas e não nulas (por exemplo int e int?).
Por motivos de compatibilidade com projetos antigos, essa nova funcionalidade deve ser habilitada manualmente quando desejável, e pode ser feita diretamente em um arquivo .cs específico, de forma global para um projeto ou para uma solution inteira.
Arquivo específico
Para ativar a funcionalidade em um arquivo .cs, devemos adicionar a notação #nullable enable nele.
Projeto inteiro
Já para adicionar em um projeto como um todo, para não precisar adicionar em cada arquivo individualmente, podemos incluir a seguinte configuração nos aquivos .csproj desejados.
<Nullable>enable</Nullable>
Solução inteira
Para ativar para a solução toda, considerando todos os projetos que compõe a solução, podemos criar um arquivo Directory.Build.props na raiz da solution e adicionar a mesma configuração.
Independente de como a configuração foi ativada, a partir deste momento o compilador passará a analisar as referências nulas e irá emitir um warning para cada erro encontrado.

Só warning basta?
Durante o desenvolvimento, caso não existam políticas de clean code bem definidas, geralmente os warnings gerados durante a codificação são ignorados, e implantar uma funcionalidade que geraria mais warnings além dos existentes não seria muito produtivo.
Porém existe uma forma de se definir que alguns warnings devem ser tratados pelo compilador como erros, e a partir de então a aplicação não irá compilar caso determinados warnings sejam gerados durante a compilação da aplicação.
Existem formas de fazer com que todos os warnings sejam tratados como erro, porém nesse caso queremos apenas que alguns erros de referência nula sejam gerados, e para tal podemos incluir mais uma configuração no .csproj ou no Directory.Build.props.
São três erros principais que queremos eliminar, então vamos transformar os warnings em erro para esses três: o erro de atribuição de um possível valor nulo à uma variável não anulável (CS8600), o erro de acessar uma referência possivelmente nula (CS8602) e o erro de possível retorno de uma referência nula (CS8603).
A nova funcionalidade traz diversos outras análises e códigos de warning, porém esses são os que mais comumente geram o erro de NullReferenceException, mas são esses três principais warnings que desejamos que sejam tratados como erro de compilação, e para isso devemos adicionar a seguinte configuração aos arquivos .csproj ou no Directory.Build.props:
<WarningsAsErrors>CS8600;CS8602;CS8603;</WarningsAsErrors>
E vamos também deixar essas configurações para que apenas fiquem ativas quando a compilação for em modo debug. Assim evitamos que qualquer warning desse impeça a compilação em ambientes produtivos.
Agora, ao compilar a aplicação, em vez de apenas warnings, o compilador irá gerar um erro para cada problema encontrado, e a aplicação não será executada, forçando que estes problemas sejam corrigidos por quem estiver fazendo o desenvolvimento.

Agora vamos corrigir os erros!
Para corrigir os problemas, nas variáveis que precisam receber um valor nulo, devemos adicionar uma interrogação na declaração do tipo para confirmar ao compilador que a variável pode aceitar nulo.
O mesmo deve ser feito na declaração do tipo de retorno do método, confirmando que ele poderá retornar um valor nulo.
Já para o erro de acesso de referência nula, devemos adicionar um tratamento para apenas acessar as propriedades de uma variável se ela não estiver nula. Com isso o compilador não irá mais gerar nenhum erro, e ao executar a aplicação ela não gerará nenhum erro.
E essa nova validação não funciona apenas para strings, mas irá olhar para todas as variáveis de tipo de referência, como class, object e dynamic.
Devo ativar essa validação no meu projeto?
Caso você esteja criando um novo projeto com o .NET Core 3.0, é fortemente recomendado que essa validação seja ativada desde o início do desenvolvimento, para assim tentar garantir que todo desenvolvedor que fizer uma alteração em um código, não importa qual o nível de experiência, fique garantido que o problema de referência nula seja minimizado.
Agora, caso você esteja migrando um projeto já existente para o .NET Core 3.0, recomenda-se avaliar o tamanho do projeto antes de fazer essa ativação.
Como normalmente essa implementação não gera erro de compilação, quando ativado pode acabar fazendo com que apareçam um numero absurdo de erros, tornando essa ativação inviável.
Nestes casos, pode-se adotar a estratégia de fazer a ativação por arquivo, apenas nos que estejam sendo alterados, para que esse problema seja observado aos poucos.
O código fonte deste exemplo está disponível no GitHub em https://github.com/lfrigodesouza/NullableReferences
