C# - Técnicas para tratamento de null
Hoje veremos algumas técnicas para tratar null e assim maximizar a robustez do código. |
No C# null é um valor especial que representa a ausência de valor ou um valor padrão para tipos de referência. Ele indica que a variável não faz referência a uma instância de um objeto e pode ser atribuída a variáveis de tipos de referência e variáveis de tipo de valor quando eles forem tipos de valor anuláveis.
O não tratamento adequado do valor null pode resultar em problemas potenciais como NullReferenceException, comportamentos ou resultados não esperados, impacto no desempenho e complexidade do código. Vejamos cada uma destas situações com mais detalhes:
Nota: Os exemplos usados foram criados no .NET 7.0 usando o Top Level Statement
1- NullReferenceException
Esta exceção é lançada quando você tenta acessar um membro de um objeto que é null;
string minhaString = null;
Console.WriteLine(minhaString.Length);
// Lança um
NullReferenceException |
Neste exemplo, a
variável minhaString recebe um valor nulo, ao
tentarmos acessar sua propriedade Length sem verificar se ela é nula resulta em
um NullReferenceException, porque você não pode
acessar membros de um objeto que for nulo.
Para evitar essa exceção, você precisa verificar se
minhaString é null antes de acessar seus membros, da seguinte forma:
string
minhaString =
null; if (minhaString != null)Console.WriteLine(minhaString.Length); ou if (minhaString is not null)Console.WriteLine(minhaString.Length); |
2- Comportamentos ou resultados não esperados
Se você não
verificar valores nulos antes de usá-los, poderá acabar com um comportamento não
intencional em seu aplicativo, como resultado ou comportamento incorreto.
Aqui está um exemplo de como o manuseio incorreto de valores nulos pode resultar
em comportamento não intencional em C#:
string
minhaString =
null; int tamanho = minhaString?.Length ?? 0;// Saída: Tamanho: 0 Console.WriteLine("Tamanho: " + tamanho); |
Neste exemplo, a
variável minhaString recebe um valor nulo, mas usamos o operador de coalesência
nula (??) para fornecer um valor padrão de 0 para a propriedade
Length se ela for nula. Isso resulta em um
comportamento indesejado, porque a propriedade Length
de uma string nula não é 0, mas uma NullReferenceException
seria gerada se tentássemos acessá-la diretamente.
Para evitar esse comportamento não intencional, é importante entender o
comportamento dos valores nulos em seu código e tratá-los adequadamente. Nesse
caso, uma abordagem melhor pode ser verificar se minhaString é null antes de
acessar sua propriedade Length ou fornecer um valor padrão que faça sentido para
seu caso de uso específico.
3- Impacto no desempenho
O tratamento
inadequado de valores nulos também pode levar a problemas de desempenho, pois
seu código pode ter que realizar verificações adicionais ou lidar com condições
inesperadas.
Aqui está um exemplo de como o manuseio incorreto de valores nulos pode resultar
em um impacto no desempenho em C#:
Aqui poderíamos ter usado a instrução if-else :
string
minhaString =
null; for (int i = 0; i < 100000000; i++){ if (minhaString != null) { int tamanho = minhaString.Length; } } |
Neste exemplo,
temos um laço que itera 100 milhões de vezes, e dentro do laço, verificamos se
minhaString é null antes de acessar sua propriedade Length. No entanto, como
minhaString é nula, essa verificação é executada
100 milhões de vezes sem fazer nada de útil. Isso resulta em um impacto
significativo no desempenho, pois o programa gasta muito tempo verificando nulo
sem realmente fazer nenhum trabalho.
Para evitar esse impacto no desempenho, é importante entender o comportamento
dos valores nulos em seu código e tratá-los adequadamente. Nesse caso, uma
abordagem melhor pode ser atribuir um valor não nulo a
minhaString para que a verificação dentro do loop não seja necessária ou
estruturar o código para que a verificação seja realizada apenas uma vez e o
resultado seja armazenado em uma variável para uso posterior.
4- Complexidade do código
O tratamento
inconsistente de valores nulos pode tornar seu código mais complexo e difícil de
manter, especialmente se exigir várias verificações de valores nulos em vários
pontos do código.
Aqui está um exemplo de como o manuseio incorreto de valores nulos pode resultar
em complexidade de código:
List<string>
minhaLista =
null; if (minhaLista is not null){ for (int i = 0; i < minhaLista.Count; i++) { string item = minhaLista[i]; if (item != null) { // código } } } |
Neste exemplo,
temos uma lista de strings e queremos executar alguma ação em cada item da
lista, caso não seja nulo. No entanto, o código tornou-se complexo devido às
várias verificações de valores nulos. Isso resulta em um risco maior de bugs,
além de tornar o código mais difícil de ler e manter.
Para evitar essa complexidade, é importante entender o comportamento dos valores
nulos em seu código e tratá-los adequadamente. Nesse caso, uma abordagem melhor
pode ser usar um laço foreach e o operador condicional nulo (?.) para
simplificar o código e torná-lo mais fácil de ler e manter:
foreach
(string
item in
minhaLista?.Where(x => x !=
null)) { //código } |
Esse código é mais
conciso, mais fácil de ler e menos sujeito a bugs, pois evita a necessidade de
várias verificações de valores nulos.
Desde seu lançamento inicial, o C# introduziu vários novos recursos para
facilitar o tratamento de problemas relacionados a valores nulos. Vejamos alguns
destes recursos.
Operador de coalescência nula (??)
Esse operador permite fornecer um valor padrão para um tipo de referência nulo,
facilitando o tratamento de casos em que um tipo de referência pode ser nulo.
Exemplo:
Aluno aluno =
new
Aluno { Nome =
"Maria"
}; string nome = aluno.Nome ?? "Desconhecido"; int idade = aluno.Idade ?? 0; Console.WriteLine( $"Nome: {nome}, Idade: {idade}");Console.ReadKey(); class Aluno{ public string Nome { get; set; } public int? Idade { get; set; } } |
Neste exemplo,
temos uma classe User com uma propriedade Nome e uma propriedade Idade que pode
ser anulada. Quando criamos uma instância da classe Aluno, apenas definimos a
propriedade Nome, mas deixamos a propriedade Idade sem atribuição, portanto, ela
é nula.
Em seguida, usamos o operador de união nula para fornecer valores padrão para as
propriedades Nome e Idade. Se a propriedade Nome for nula, definimos como
"Desconhecido". Se a propriedade Idade for nula, definimos como 0.
O resultado é que o valor de nome é "Maria" e o valor de Idade é 0. Isso permite
lidar com valores nulos de maneira concisa e legível, sem ter que escrever
várias verificações para valores nulos.
Operador condicional nulo (?.)
O operador condicional nulo (?.) fornece uma maneira concisa de acessar uma propriedade ou método de um objeto, sem a necessidade de verificar valores nulos primeiro. Exemplo:
class
Aluno { public string Nome { get; set; } public Endereco Endereco { get; set; } } class Endereco{ public string Local { get; set; } } Aluno aluno = new Aluno { Nome = "Maria" };string local = aluno?.Endereco?.Local;Console.WriteLine(local ?? "Desconhecido"); |
Aqui,
temos uma classe Aluno com uma propriedade Nome e uma propriedade Endereco.
Quando criamos uma instância da classe Aluno , apenas definimos a propriedade
Nome, mas deixamos a propriedade Endereco sem atribuição, portanto, é nula.
Em seguida, usamos o operador condicional nulo para acessar a propriedade Local
do objeto Endereco, sem precisar verificar valores nulos primeiro. Se aluno
ou aluno.Endereco for nulo, o resultado da expressão será nulo e o ??
operador é usado para fornecer um valor padrão de "Desconhecido".
O resultado é que o valor de local é nulo e a saída é "Desconhecido".
Isso permite acessar propriedades e métodos de um objeto sem ter que escrever
várias verificações para valores nulos e reduz a complexidade do código.
Tipos de referência não anuláveis
No C# 8.0, foram introduzidos tipos de referência não anuláveis, que permitem
especificar que um tipo de referência não deve ser nulo quando for usado. Depois
de habilitar esse recurso, todos os tipos de referência são não anuláveis por
padrão, a menos que você especifique explicitamente que é anulável.
Isso ajuda a evitar que NullReferenceExceptions ocorra em seu código. Aqui está um exemplo de uso de tipos de referência não anuláveis:
Aluno aluno =
new
Aluno(); //sem alerta do compilador string nome = aluno.Nome; // requer a explicida verificação da não nulidade Endereco endereco = aluno.Endereco!; Console.WriteLine($"Nome: {nome}, Local: {endereco.Local}"); Console.ReadKey(); class Aluno{ public string Nome { get; set; } = string.Empty; public Endereco? Endereco { get; set; } } class Endereco{ public string Local { get; set; } = string.Empty; } |
Neste exemplo,
temos uma classe Aluno com uma propriedade Nome e uma propriedade Endereco. A
propriedade Nome é declarada com um tipo de referência não anulável e é
inicializada com uma string vazia. A propriedade Endereco é declarada com um
tipo de referência anulável e não é inicializada, portanto, é nula por padrão.
Em seguida, criamos uma instância da classe Aluno e a atribuímos à variável
aluno. Quando acessamos a propriedade Nome, o compilador não emite um alerta,
pois é um tipo de referência não anulável e é inicializado.
No entanto, quando acessamos a propriedade Endereco, precisamos usar o
operador tolerância nula (!) para
verificar explicitamente se não é nula, ou o compilador emitirá um aviso. Isso
garante que estejamos cientes do valor nulo potencial e evita comportamento não
intencional.
O resultado é que o valor de nome é uma string vazia e o valor de
endereco é nulo. Isso nos permite garantir que as variáveis de tipo de
referência não sejam nulas, a menos que explicitamente permitidas, e reduz a
probabilidade de NullReferenceException.
O recurso de tipos de referência não anuláveis é ativado por padrão após o .NET
6. Você pode desativá-lo no nível do projeto adicionando a configuração de
projeto <Nullable>disable</Nullable>. Em um único
arquivo de origem C#, você pode adicionar #nullable
disablepragma para desabilitar o contexto anulável
Operador que tolerância nula (!)
O operador de tolerância nula (!) é usado para suprimir a verificação nula
ao acessar um tipo de referência anulável. Esse operador permite que você acesse
uma propriedade ou chame um método de um tipo de referência anulável sem
primeiro verificar se ele é nulo.
Aluno aluno =
new
Aluno(); Endereco? endereco = aluno.Endereco; if (endereco != null){ Console.WriteLine($"Local: {endereco.Local}"); } //Usando o null-forgiving operator Console.WriteLine($"Local: {aluno.Endereco!.Local}"); Console.ReadKey(); class Aluno{ public string Nome { get; set; } = string.Empty; public Endereco? Endereco { get; set; } } class Endereco { public string Local { get; set; } = string.Empty; } |
Neste exemplo,
temos uma classe Aluno com uma propriedade Nome e uma propriedade Endereco. A
propriedade Endereco é declarada como um tipo de referência anulável e não é
inicializada, portanto, é nula por padrão.
Em seguida, criamos uma instância da classe Aluno e a atribuímos à variável
aluno. Acessamos a propriedade Endereco e atribuímos à variável endereco.
Em seguida, verificamos se o endereço é nulo e, se não for, acessamos a
propriedade Local.
Por fim, usamos o operador de tolerância nula para acessar a propriedade Local
da propriedade Endereco diretamente, sem verificar se há nulo. Se Endereco for
nulo, um System.NullReferenceException será lançado
em tempo de execução.
O operador de tolerância nulo pode ser útil em certos casos, como quando você
tem certeza de que um tipo de referência não é nulo ou quando
deseja lançar uma exceção se for nulo. No entanto, geralmente é recomendável
usar operadores de união nula ou tipos de referência não anuláveis
para lidar com valores nulos, pois essas abordagens são mais seguras e
explícitas.
Esses recursos, juntamente com o restante da linguagem e do runtime, fornecem um conjunto de ferramentas poderosas e flexíveis para lidar com valores nulos em C#.
E estamos conversados...
"Então Jesus disse: "Quando vocês levantarem o Filho do homem, saberão que Eu
Sou, e que nada faço de mim mesmo, mas falo exatamente o que o Pai me ensinou."
João 8:28
Referências:
NET - Unit of Work - Padrão Unidade de ...