Tipos de referência anuláveis (referência C#)

Nota

Este artigo abrange tipos de referência anuláveis. Você também pode declarar tipos de valor anuláveis.

Use tipos de referência nullable em código que esteja num contexto consciente de nullable. Tipos de referência anuláveis, os avisos de análise estática nula e o operador de perdão nula são recursos de linguagem opcionais. Todos estão desligados por defeito. Controlas um contexto anulável ao nível do projeto usando definições de build, ou no código usando pragmas.

A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em pré-visualizações públicas para o próximo lançamento linguístico.

A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.

Sugestão

Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.

Importante

Todos os modelos de projeto habilitam o contexto anulável para o projeto. Projetos criados com modelos anteriores não incluem esse elemento e esses recursos estão desativados, a menos que você os habilite no arquivo de projeto ou use pragmas.

Em um contexto consciente anulável:

  • Deve inicializar uma variável de um tipo T de referência com um valor não nulo, e nunca poderá atribuir um valor que possa ser null.
  • Podes inicializar uma variável de um tipo T? de referência com null ou atribuir null, mas deves compará-la null antes de desreferenciar.
  • Quando se aplica o operador de perdão nulo a uma variável m do tipo T?, como em m!, a variável é considerada não nula.

O compilador impõe as distinções entre um tipo T de referência não anulável e um tipo T? de referência anulável usando as regras anteriores. Uma variável do tipo T e uma variável do tipo T? são do mesmo tipo .NET. O exemplo a seguir declara uma cadeia de caracteres não anulável e uma cadeia de caracteres anulável e, em seguida, usa o operador de perdão nula para atribuir um valor a uma cadeia de caracteres não anulável:

string notNull = "Hello";
string? nullable = default;
notNull = nullable!; // null forgiveness

As variáveis notNull e nullable ambas usam o String tipo. Como os tipos não anuláveis e anuláveis usam ambos o mesmo tipo, não se pode usar um tipo de referência anulável em vários locais. Em geral, não podes usar um tipo de referência anulável como classe base ou interface implementada. Não podes usar um tipo de referência anulável em nenhuma expressão de criação de objetos ou teste de tipos. Não podes usar um tipo de referência anulável como tipo de expressão de acesso membro. Os exemplos a seguir mostram essas construções:

public MyClass : System.Object? // not allowed
{
}

var nullEmpty = System.String?.Empty; // Not allowed
var maybeObject = new object?(); // Not allowed
try
{
    if (thing is string? nullableString) // not allowed
        Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
    Console.WriteLine("error");
}

Referências anuláveis e análise estática

Os exemplos na seção anterior ilustram a natureza dos tipos de referência anuláveis. Os tipos de referência anuláveis não são novos tipos de classe, mas sim anotações em tipos de referência existentes. O compilador usa essas anotações para ajudá-lo a encontrar possíveis erros de referência nula em seu código. Não há diferença de tempo de execução entre um tipo de referência não anulável e um tipo de referência anulável. O compilador não adiciona nenhuma verificação de tempo de execução para tipos de referência não anuláveis. Os benefícios estão na análise em tempo de compilação. O compilador gera avisos que ajudam você a encontrar e corrigir possíveis erros nulos em seu código. Você declara sua intenção e o compilador avisa quando seu código viola essa intenção.

Importante

As anotações de referência anuláveis não introduzem alterações de comportamento, mas outras bibliotecas podem usar reflexão para produzir comportamentos diferentes em tempo de execução para tipos de referência nuláveis e não anuláveis. Notavelmente, o Entity Framework Core lê atributos anuláveis. Ele interpreta uma referência anulável como um valor opcional e uma referência não anulável como um valor necessário.

Em um contexto habilitado anulável, o compilador executa análise estática em variáveis de qualquer tipo de referência, nulas e não anuláveis. O compilador rastreia o estado nulo de cada variável de referência como não-nulo ou talvez-nulo. O estado padrão de uma referência não anulável não é nulo. O estado padrão de uma referência anulável é talvez-nulo.

Os tipos de referência não anuláveis devem ser sempre seguros para cancelar a referência porque seu estado nulo não é nulo. Para impor essa regra, o compilador emite avisos se um tipo de referência não anulável não for inicializado com um valor não nulo. Deve atribuir variáveis locais onde as declara. A cada campo deve ser atribuído um valor não-nulo , em um inicializador de campo ou em cada construtor. O compilador emite avisos quando uma referência não anulável é atribuída a uma referência cujo estado é talvez-nulo. Geralmente, uma referência não anulável não é nula e não são emitidos avisos quando se desreferenciam essas variáveis.

Nota

Se você atribuir uma expressão maybe-null a um tipo de referência não anulável, o compilador gerará um aviso. Em seguida, o compilador gera avisos para essa variável até que ela seja atribuída a uma expressão não-nula .

Podes inicializar ou atribuir null a tipos de referência anuláveis. Portanto, a análise estática deve determinar que uma variável não é nula antes de ser desreferenciada. Se uma referência anulável for determinada como talvez-nula, atribui-la a uma variável de referência não anulável gera um aviso do compilador. A classe a seguir mostra exemplos desses avisos:

public class ProductDescription
{
    private string shortDescription;
    private string? detailedDescription;

    public ProductDescription() // Warning! shortDescription not initialized.
    {
    }

    public ProductDescription(string productDescription) =>
        this.shortDescription = productDescription;

    public void SetDescriptions(string productDescription, string? details=null)
    {
        shortDescription = productDescription;
        detailedDescription = details;
    }

    public string GetDescription()
    {
        if (detailedDescription.Length == 0) // Warning! dereference possible null
        {
            return shortDescription;
        }
        else
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
    }

    public string FullDescription()
    {
        if (detailedDescription == null)
        {
            return shortDescription;
        }
        else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
        return shortDescription;
    }
}

O trecho a seguir mostra onde o compilador emite avisos ao usar essa classe:

string shortDescription = default; // Warning! non-nullable set to null;
var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription maybe null.

string description = "widget";
var item = new ProductDescription(description);

item.SetDescriptions(description, "These widgets will do everything.");

Os exemplos anteriores demonstram como a análise estática do compilador determina o estado nulo das variáveis de referência. O compilador aplica regras de linguagem para verificações e atribuições nulas para informar sua análise. O compilador não pode fazer suposições sobre a semântica de métodos ou propriedades. Se você chamar métodos que executam verificações nulas, o compilador não pode saber que esses métodos afetam o estado nulo de uma variável. Pode adicionar atributos às suas APIs para informar o compilador sobre a semântica dos argumentos e valores de retorno. Muitas APIs comuns nas bibliotecas .NET têm esses atributos. Por exemplo, o compilador interpreta corretamente IsNullOrEmpty como uma verificação nula. Para obter mais informações sobre os atributos que se aplicam à análise estática de estado nulo, consulte o artigo sobre Atributos anuláveis.

Contexto anulável

O contexto nulo determina como o compilador lida com as anotações de tipos de referência anuláveis e que avisos produz durante a análise estática de estado nulo. O contexto anulável contém dois flags: a configuração de anotação e a configuração de aviso.

Tanto as configurações de anotação quanto as de aviso são desabilitadas por padrão para projetos existentes. A partir do .NET 6 (C# 10), ambos os flags são habilitados por padrão para novos projetos. A razão para dois sinalizadores distintos para o contexto anulável é facilitar a migração de grandes projetos anteriores à introdução de tipos de referência anuláveis.

Para projetos pequenos, você pode habilitar tipos de referência anuláveis, corrigir avisos e continuar. No entanto, para projetos maiores e soluções multi-projeto, esse processo pode gerar um grande número de avisos. Você pode usar pragmas para habilitar tipos de referência anuláveis arquivo por arquivo quando começar a usar tipos de referência anuláveis. Os novos recursos que protegem contra o lançamento de um System.NullReferenceException podem causar interrupções quando ativados em uma base de código existente:

  • Todas as variáveis de referência explicitamente tipadas são interpretadas como tipos de referência não anuláveis.
  • O significado da class restrição em genéricos mudou para significar um tipo de referência não anulável.
  • Novos avisos são gerados por causa dessas novas regras.

O contexto de anotação anulável determina o comportamento do compilador. Há quatro combinações para as configurações do contexto anulável :

  • ambos desativados: O código é nullable-oblivious. Desabilitar corresponde ao comportamento antes que os tipos de referência anuláveis fossem habilitados, exceto que a nova sintaxe produz avisos em vez de erros.
    • Os avisos anuláveis estão desativados.
    • Todas as variáveis de tipo de referência são tipos de referência anuláveis.
    • O uso do sufixo ? para declarar um tipo de referência anulável produz um aviso.
    • Você pode usar o operador de perdão nulo, !mas ele não tem efeito.
  • ambos ativados: O compilador ativa todas as análises de referências nulas e todos os recursos da linguagem.
    • Todos os novos avisos anuláveis estão habilitados.
    • Você pode usar o sufixo ? para declarar um tipo de referência anulável.
    • As variáveis de tipo de referência sem o sufixo ? são tipos de referência não anuláveis.
    • O operador de perdão de nulos suprime avisos de um possível desreferenciamento de null.
  • aviso ativado: O compilador executa todas as análises nulas e emite avisos quando o código pode desreferenciar null.
    • Todos os novos avisos anuláveis estão habilitados.
    • O uso do sufixo ? para declarar um tipo de referência anulável produz um aviso.
    • Todas as variáveis de tipo de referência podem ser nulas. No entanto, os membros têm o estado nulo de não-nulo no início de todos os métodos, a menos que seja declarado com o sufixo ?.
    • Você pode usar o operador de perdão nulo, !.
  • anotações ativadas: O compilador não emite avisos quando o código pode desreferenciar nullou quando se atribui uma expressão que pode ser nula a uma variável não anulável.
    • Todos os novos avisos anuláveis estão desativados.
    • Você pode usar o sufixo ? para declarar um tipo de referência anulável.
    • As variáveis de tipo de referência sem o sufixo ? são tipos de referência não anuláveis.
    • Você pode usar o operador de perdão nulo, !mas ele não tem efeito.

Pode definir o contexto de anotação anulável e o contexto de aviso anulável para um projeto usando o <Nullable> elemento no seu ficheiro .csproj . Este elemento configura como o compilador interpreta a nulidade dos tipos e que avisos emite. A tabela a seguir mostra os valores permitidos e resume os contextos que eles especificam.

Contexto Advertências de desreferenciação Avisos de atribuição Tipos de referência ? sufixo ! Operador
disable Disabled Disabled Todos são anuláveis Gera um aviso Não tem efeito
enable Ativado Ativado Não anulável, a menos que declarado com ? Declara o tipo anulável Suprime avisos para possível null atribuição
warnings Ativado Não aplicável Todos são anuláveis, mas os membros são considerados não-nulos na chave de abertura dos métodos Gera um aviso Suprime avisos para possível null atribuição
annotations Disabled Disabled Não anulável, a menos que declarado com ? Declara o tipo anulável Não tem efeito

As variáveis de tipo de referência no código compilado em um contexto desabilitado são anuláveis-ignorantes. Você pode atribuir uma variável null literal ou uma variável talvez-nula a uma variável que seja ignorante sobre anulabilidade. No entanto, o estado padrão de uma variável anulável-esquecida não é nulo.

Escolha o cenário que melhor se adequa ao seu projeto:

  • Escolha desativar para projetos herdados que você não deseja atualizar com base em diagnósticos ou novos recursos.
  • Escolha avisos para determinar onde seu código pode lançar System.NullReferenceExceptions. Você pode abordar esses avisos antes de modificar o código para habilitar tipos de referência não anuláveis.
  • Escolha anotações para expressar sua intenção de design antes de ativar os avisos.
  • Escolha habilitar para novos projetos e projetos ativos onde você deseja proteger contra exceções de referência nula.

Exemplo:

<Nullable>enable</Nullable>

Você também pode usar diretivas para definir esses mesmos sinalizadores em qualquer lugar do código-fonte. Essas diretivas são mais úteis quando você está migrando uma base de código grande.

  • #nullable enable: Define os sinalizadores de anotação e aviso para ativar.
  • #nullable disable: Define os sinalizadores de anotação e aviso para desativar.
  • #nullable restore: Restaura o sinalizador de anotação e o sinalizador de aviso para as configurações do projeto.
  • #nullable disable warnings: Define a bandeira de aviso para desativar.
  • #nullable enable warnings: Define a bandeira de aviso para ativar.
  • #nullable restore warnings: Restaura o sinalizador de aviso para as configurações do projeto.
  • #nullable disable annotations: Define a bandeira de anotação para desativar.
  • #nullable enable annotations: Define a flag de anotação para ativar.
  • #nullable restore annotations: Restaura o sinalizador de anotação para as configurações do projeto.

Para qualquer linha de código, você pode definir qualquer uma das seguintes combinações:

Sinalizador de aviso Indicador de anotação Use
padrão do projeto padrão do projeto Predefinição
activar desactivar Corrigir os avisos de análise
activar padrão do projeto Corrigir os avisos de análise
padrão do projeto activar Adicionar anotações de tipo
activar activar Código já migrado
desactivar activar Anotar código antes de corrigir avisos
desactivar desactivar Adicionando código herdado ao projeto migrado
padrão do projeto desactivar Raramente
desactivar padrão do projeto Raramente

Estas nove combinações dão-lhe controlo detalhado sobre os diagnósticos que o compilador emite para o seu código. Pode ativar mais funcionalidades em qualquer área que esteja a atualizar, sem ver mais avisos que ainda não está pronto para abordar.

Importante

O contexto global anulável não se aplica a ficheiros de código gerados. Em qualquer uma das estratégias, o contexto anulável é desabilitado para qualquer arquivo de origem marcado como gerado. Esta condição significa que o compilador não anota quaisquer APIs nos ficheiros gerados. O compilador não produz avisos anuláveis para ficheiros gerados. Um ficheiro é marcado como gerado de qualquer uma das seguintes quatro formas:

  1. No .editorconfig, especifique generated_code = true em uma seção que se aplica a esse arquivo.
  2. Coloque <auto-generated> ou <auto-generated/> em um comentário na parte superior do arquivo. Ele pode estar em qualquer linha nesse comentário, mas o bloco de comentários deve ser o primeiro elemento no arquivo.
  3. Inicie o nome do arquivo com TemporaryGeneratedFile_
  4. Termine o nome do arquivo com .designer.cs, .generated.cs, .g.cs ou .g.i.cs.

Os geradores podem optar por aderir usando a #nullable diretiva do pré-processador.

Por padrão, as anotações de nulidade e os sinalizadores de aviso estão desativados. Esse padrão significa que o seu código existente compila sem alterações e sem gerar novos avisos. A partir do .NET 6, os novos projetos incluem o elemento <Nullable>enable</Nullable> em todos os modelos de projeto, definindo esses sinalizadores como ativados.

Essas opções fornecem duas estratégias distintas para atualizar uma base de código existente para usar tipos de referência anuláveis.

Definindo o contexto anulável

Podes controlar o contexto nulo de duas formas. Ao nível do projeto, adiciona a <Nullable>enable</Nullable> definição do projeto. Num único ficheiro fonte C#, adiciona-se o #nullable enable pragma para ativar o contexto anulável. Para mais informações, veja definir uma estratégia anulável. Antes do .NET 6, novos projetos usam o padrão, <Nullable>disable</Nullable>. A partir do .NET 6, novos projetos incluem o <Nullable>enable</Nullable> elemento no arquivo de projeto.

Genéricos

Quando se usa um parâmetro de tipo, T, como seu equivalente anulável, T?, o argumento do tipo real determina como o ? é interpretado. Considere a seguinte declaração genérica:

public class Box<T>
{
    public T Contents { get; set; }
}

Como um parâmetro de tipo pode representar tanto um tipo de referência como um tipo de valor, o significado de T? depende do argumento de tipo que o chamador fornece. As seguintes regras descrevem o que T? resolve para quando T não tem restrições:

  • O argumento do tipo é um tipo de referência não anulável. Para Box<string>, T é string e T? é string?— o tipo de referência anulável correspondente.
  • O argumento do tipo é um tipo de valor. Para Box<int>, T é int e T? é também int—o mesmo tipo de valor. A anotação não tem efeito nos tipos de valor, a menos que o parâmetro de tipo tenha a struct restrição, caso em T? que significa Nullable<T> (int?).
  • O argumento do tipo já é anulável. Para Box<string?>, T é string? e T? ainda string?é . Não tens um tipo "duplamente anulável".

As restrições restringem que tipo de argumentos são permitidos. Também permitem ao compilador raciocinar sobre como T pode ser usado:

  • where T : class requer um tipo de referência não anulável. Box<string> é permitido; Box<string?> produz um aviso.
  • where T : class? permite um tipo de referência nulo ou não anulável. Ambos Box<string> e Box<string?> são permitidos.
  • where T : struct requer um tipo de valor não anulável. Box<int> é permitido; Box<int?> não é. Com esta restrição, T? dentro da média Nullable<T>genérica —para Box<int>, T? é int?.
  • where T : notnull requer uma referência ou tipo de valor não anulável. Box<string> e Box<int> são permitidos; Box<string?> produz um aviso.
  • where T : BaseType requer um tipo de referência não anulável que derive de BaseType. Adicione ? (where T : BaseType?) para permitir também tipos derivados anuláveis.

As restrições ajudam o compilador a raciocinar sobre como um parâmetro genérico de tipo é utilizado:

public static T? FirstOrDefault<T>(IEnumerable<T> source)
{
    foreach (T item in source)
    {
        return item;
    }
    return default;
}

public static void RequireNotNull<T>(T value) where T : notnull
{
    ArgumentNullException.ThrowIfNull(value);
}

public static void Generics()
{
    string? first = FirstOrDefault<string>([]);
    Console.WriteLine(first ?? "<empty>");

    RequireNotNull("not null");
}

Especificação da linguagem C#

Para mais informações, consulte a secção Tipos de referência anuláveis da especificação da linguagem C#.

Consulte também