C# - Null, Nullable Value, Nullable Reference, Null-Coalescing e Null-Conditional

 Hoje vamos recordar os conceitos relacionados com a palavra-chave Null, os tipos Nullable Value, os tipos Nullable Reference, o operador Null Coalescing e o operador Null-Conditional.

As nulabilidades dos tipos

Qualquer tipo de referência pode ter um dos seguintes valores quanto a nulidade que descreve quando os avisos são gerados:

Nonnullabe ou Não anulável: Null ou nulo não pode ser atribuído a variáveis desse tipo. Variáveis desse tipo não precisam ser verificadas quanto a nulidade (ser igual a null) antes de serem desreferenciadas;

Nullabe ou Anulável: Null ou nulo pode ser atribuído a variáveis deste tipo. Desfazer a referência das variáveis desse tipo sem primeiro verificar se há null ou nulo vai causar um aviso;

Oblivious Oblivious é o estado pré-C# 8.0. Variáveis desse tipo podem ser referenciadas ou atribuídas sem avisos;

Uknown ou Desconhecido: Desconhecido geralmente é usado para parâmetros de tipo em que as restrições não informam ao compilador que o tipo deve ser anulável ou não anulável.;

A nulidade de um tipo em uma declaração de variável é controlada pelo contexto anulável em que a variável for declarada.

A seguir vamos recordar o comportamento dos Nulls, começando com a palavra-chave Null.

1- Null Keyword

A palavra-chave null é usada para representar uma referência nula, que é uma referência que não se refere(nossa...) a nenhum objeto. Ela só pode ser atribuída a variáveis de tipo de referência e não para variáveis de tipo de valor.

string s = null;

Tentar acessar um objeto que tem uma referência a null causará uma exceção, porque não há instância válida para desfazer a referência.

int tamanho = lado.Length;    // erro: NullReferenceException

A fim de acessar com segurança os membros da instância de um objeto que pode ser null,  a primeira coisa a fazer é verificar se existe uma referência nula.

Este teste pode ser feito, por exemplo, usando o operador igual a (==) :

using System;
namespace CShpNulls
{
    class Program
    {
        static void Main(string[] args)
        {
            Teste teste = new Teste();
            if (teste.lado == null)
            {
                // cria um objeto valido(string vazia)
                teste.lado = ""; 
            }
            int tamanho = teste.lado.Length; // 0
            Console.ReadKey();
        }
    }
    class Teste
    {
        public string lado;
    }
}

No método Main criamos uma instância da classe Teste e a seguir verificamos se o campo lado é null usando o operador ==.

Outra opção é usar o operador ternário (? :)  para atribuir um valor adequado no caso de um null for encontrado para a string:

string lado = null;
int tamanho = (lado != null) ? lado.Length : 0; // (se lado for diferente de null atribui o tamanho senão atribui zero)

2- Nullabe Value Types

Um tipo de valor pode ser feito para tratar o valor null além de seu intervalo normal de valores anexando um ponto de interrogação (?) ao seu tipo subjacente. Ex:  int?

Isso é chamado de tipo anulável ou Nullable Value, e permite os tipos simples, bem como tipos struct, para indicar um valor indefinido.

Por exemplo, bool? é um  tipo anulável que pode conter os valores true, false e null.

3- Nullabe Reference Types

Um dos erros mais comuns nas linguagens do paradigma da programação orientada a objetos é desfazer a referencia uma variável definida como nula, o que causa uma exceção de referência nula.

Para ajudar a evitar esse problema, o  C# 8.0 introduziu um distinção entre tipos de referência anuláveis e não anuláveis.

Assim da mesma forma que os tipos de valor anuláveis, um tipo de referência anulável é criado anexando um ponto de interrogação (?) para o tipo e dessa forma apenas a esse tipo de referência pode ser atribuiu o valor null.

string? s1 = null;   // tipo de referência anulável ou nullable reference type
string s2 = "";        // tipo de referência não anulável ou non-nullable reference type


Este recurso precisa ser explicitamente ativado porque existem os tipos de referência que então se tornam tipos de referência não anuláveis.

Para habilitar o recurso em todo o projeto, clique com o botão direito do mouse no item do projeto no Solution Explorer e
selecione Editar Arquivo de Projeto no menu de contexto para abrir o arquivo .csproj.

Neste arquivo, adicione um elemento Nullable ao elemento PropertyGroup e defina seu valor para enable:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Outras configurações válidas são :

warnings : O contexto de anotação anulável está desativado. O contexto de aviso anulável está ativado. Variáveis ​​de um tipo de referência são esquecidas. Todos os avisos de anulação estão habilitados.

annotations: O contexto de anotação anulável está ativado. O contexto de aviso anulável está desativado. Variáveis ​​de um tipo de referência, string por exemplo, não são anuláveis. Todos os avisos de anulação são desativados.

disable: O contexto de anotação anulável está desativado. O contexto de aviso anulável está desativado. As variáveis ​​de um tipo de referência são esquecidas, assim como as versões anteriores do C#. Todos os avisos de anulação são desativados.

Como alternativa, o recurso pode ser habilitado para apenas um único arquivo adicionando a diretiva #nullable enable a esse arquivo.

Uma vez ativado, qualquer atribuição de null a tipos de referência não anuláveis irá desencadear um alerta de compilação ou warning.

Ex:
#nullable enable
string a = null; // Atenção

Agora, os tipos de referência não anuláveis não precisam ser verificados para valores nulos antes eles de serem desreferenciados.

Tentar remover a referência de uma referência anulável em contextos quando o valor pode ser um null causará um aviso do compilador, e, para remover o aviso é preciso realizar uma verificação de valor null.

Este comportamento pode ser substituído usando o operador null-forgiving (!) adicionado no C# 8.0.

Nos casos em que o compilador não poder determinar que uma variável não é nula, este operador postfix pode ser usado para suprimir o aviso quando você tiver certeza de que a variável anulável não está definida como nula.

 #nullable enable
string? d = "Olá";
//...
int a = d.Length;   // alerta

int b = d!.Length;  // sem alerta
 

4- Null-Coalescing Operator

O operador de coalescência nula (??) retorna o operando à esquerda se for não nulo e, caso contrário, retorna o operando à direita.

Este operador condicional fornece uma sintaxe fácil para atribuir um tipo anulável a um tipo não anulável.

int? i = null;

int j = i ?? 0;    // atribui o valor zero

Agora, uma variável de tipo anulável não deve ser convertida explicitamente em um tipo não anulável.  Isso causará um erro de tempo de execução se a variável for nula como seu valor.

O C# 8.0 introduziu o operador de atribuição de coalescência nula (?? =), combinando o operador de coalescência nula com uma atribuição.

Este operador atribui o valor do lado direito ao operando do lado esquerdo se o operando do lado esquerdo for avaliado como nulo.

5- Null-Conditional Operator

Na versão 6.0 do  C# foi introduzido o operador nulo-condicional (?.).

Este operador fornece uma maneira concisa de realizar verificações de nulos ao acessar um objeto.

Ele funciona como o operador de acesso de membro regular(.), exceto que se uma referência nula for encontrada, o valor nulo será retornado em vez de causar uma exceção.

string s = null;
int? length = s?.Length; 
   // atribui null

Combinar este operador com o operador de coalescência nula é útil para atribuir um valor padrão sempre que uma referência nula aparecer.

string s = null;
int length = s?.Length ?? 0;    
// atribui zero

Outro uso para o operador nulo-condicional são os arrays.

O ponto de interrogação(?) pode ser colocado antes dos colchetes([]) do array e a expressão será avaliada como nula se o array não for inicializado:

string[] s = null;
string s3 = s?[3];    
 // atribui null

E estamos conversados...

"E não comuniqueis com as obras infrutuosas das trevas, mas antes condenai-as. Porque o que eles fazem em oculto até dizê-lo é torpe. Mas todas estas coisas se manifestam, sendo condenadas pela luz, porque a luz tudo manifesta."
Efésios 5:11-13

Referências:


José Carlos Macoratti