Introdução
Softwares podem falhar em dois momentos diferentes: tempo de compilação e tempo de execução. No contexto de erros, nulo é um problema porque os efeitos de não tratá-los corretamente são percebidos apenas em tempo de execução.
Neste post, mostrarei como usar um recurso da linguagem C# para mover os erros nulos para o tempo de compilação e nos ajudar a evitá-los em tempo de execução.
O problema com nulo
Nulo é um valor que não é um valor. Ele é usado por tipos de referência para indicar que a variável não está apontando para nenhum valor.
Isso cria um caso especial que precisa ser verificado toda vez que a variável é acessada ou lançará uma exceção em tempo de execução quando o valor for nulo.
Este código, por exemplo, será compilado normalmente:
|
|
Mas lançará uma NullReferenceException em tempo de execução:
Exceção não tratada. System.NullReferenceException:
Referência de objeto não definida para uma instância de um objeto.
A solução é verificar se é null
antes de acessar qualquer propriedade ou método de tipo de referência:
|
|
O problema é lembrar de verificar se é nulo toda vez que um objeto pode ser nulo. É aí que os Tipos de referência anuláveis vêm para o resgate.
Tipos de referência anuláveis
Tipos de referência, como o nome sugere, armazenam referências (ponteiros) para seus dados.
C# fornece os seguintes tipos de referência:
- string
- object
- class
- record
- interface
- delegate
- dynamic
Todos esses tipos podem ter null
atribuído a eles, tornando-os um risco para uma NullReferenceException
.
C# 8 introduziu os Tipos de Referência Anuláveis. É uma maneira de dizer que as variáveis podem conter nulo e receber avisos toda vez que seus membros são acessados sem verificar se há nulos.
Para declarar tipos de referência anuláveis, usamos ?
após o nome do tipo:
string? stringThatMayBeNull = null; //String anulável
Person? personThatMayBeNull = null; //Pessoa anulável
List<Person>? personThatMayBeNull = null; //Lista anulável de Pessoa não anulável
List<Person?> personThatMayBeNull = new(){ null }; //Lista não anulável de Pessoa anulável
Para habilitar os tipos de referência anuláveis, basta adicionar a seguinte propriedade aos seus projetos:
<Nullable>enable</Nullable>
Avisos de anulabilidade
Quando habilitamos os tipos de referência anuláveis em nossos projetos, começamos a receber avisos ao atribuir null
a tipos não anuláveis:
Também começamos a receber avisos ao acessar um membro de tipo anulável quando ele pode ser nulo:
O compilador sabe quando uma variável não é nula.
Por exemplo, quando verificamos se é nulo em uma instrução if:
E quando atribuímos um valor não anulável a uma variável anulável:
Tratando nulos
Além de verificar se há nulos com instruções if
, podemos usar operadores nulos que o C# fornece para tornar o código mais limpo.
Operador condicional nulo
O operador condicional nulo (?.
) nos permite acessar um membro de tipo de referência somente se seu valor não for nulo.
|
|
Operador de perdão nulo
O operador de perdão nulo (!.
) informa ao compilador que temos certeza de que uma variável de tipo anulável que pode ser nula não é nula.
Isso é comumente útil ao escrever testes automatizados porque você conhece os resultados que serão retornados.
|
|
Operador de coalescência nula
O operador de coalescência (??
) é usado para atribuir o valor do operando direito se o valor do operando esquerdo for nulo. É usado para atribuir um tipo de referência anulável a um tipo de referência não anulável, voltando a um valor padrão em caso de nulo.
|
|
Impondo verificações nulas em tempo de compilação
Para impor verificações nulas em todos os lugares, podemos aumentar a severidade dos avisos para Error
. Desta forma, não será possível compilar os projetos sem verificar adequadamente se há nulos.
Infelizmente, as mensagens do compilador para anuláveis não estão nas categorias de qualidade de código e estilo de código explicadas em meu post anterior e não podem ser definidas como erros, a menos que sejam feitas por código de mensagem:
|
|
Para fazer isso, defina a propriedade TreatWarningsAsErrors
como Nullable
em todos os projetos:
<TreatWarningsAsErrors>Nullable</TreatWarningsAsErrors>
Depois disso, todos os avisos de mensagens de anulabilidade terão sua severidade igual a Error
:
💡 Podemos definir
TreatWarningsAsErrors
comotrue
para tratar todos os avisos como erros.
Propriedades obrigatórias
Com os tipos anuláveis habilitados, receberemos os seguintes erros em classes/registros que têm tipos de referência não anuláveis não inicializados no construtor ou com valores padrão:

C# 11 introduziu o modificador required
. Ele nos permite ter tipos de referência não anuláveis sem inicializá-los no construtor, forçando-nos a especificar os valores para essas propriedades ao instanciar um objeto.
Primeiro, adicionamos o modificador required
às propriedades:
Então, ao instanciar o objeto, precisamos passar os valores para as propriedades obrigatórias:
Se não especificarmos os valores, o compilador nos avisará:

Testes automatizados
Vale a pena notar que o uso do operador condicional nulo e do operador de coalescência nula influenciará a cobertura de código do projeto. Eles serão tratados como branches e terão que ter testes para ambos os cenários.
Por exemplo, se tivermos esta classe boba:
|
|
E este teste:
|
|
A cobertura de código será de apenas 50% porque o teste não está cobrindo o cenário de recebimento de nulo.
Criando um novo teste para o cenário nulo:
|
|
A cobertura de código será de 100% para esta classe: