Tipos de referência anulável (referência do C#)

Observação

Este artigo aborda os tipos de referência anulável. Você também pode declarar os tipos de valor anulável.

Os tipos de referência anulável estão disponíveis em código que aceitou um contexto com reconhecimento anulável. Os tipos de referência anulável, os avisos de análise estática nula e o operador tolerante a nulo são recursos de linguagem opcionais. Todos eles estão desativados por padrão. Um contexto anulável é controlado no nível do projeto, usando as configurações de compilação ou em código usando pragmas.

Importante

Todos os modelos de projeto que começam com o .NET 6 (C# 10) habilitam o contexto anulável para o projeto. Os projetos criados com modelos anteriores não incluem esse elemento, e esses recursos ficam desativados até que você os habilite no arquivo de projeto ou use pragmas.

Em um contexto com reconhecimento anulável:

  • Uma variável de um tipo de referência T deve ser inicializada com não nulo e pode nunca ser atribuída a um valor que possa ser null.
  • Uma variável de um tipo de referência T? pode ser inicializada com null ou atribuída a null, mas é necessário que seja verificada em relação a null, antes de desreferenciar.
  • Uma variável m do tipo T? é considerada não nula, quando você aplica o operador tolerante a nulo, como em m!.

As distinções entre um tipo de referência não anulável T e um tipo de referência anulável T? são impostas pela interpretação do compilador das regras anteriores. Uma variável do tipo T e uma variável do tipo T? são representadas pelo mesmo tipo do .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 tolerante a nulo 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 são representadas pelo tipo String. Como os tipos não anulável e anulável são armazenados como o mesmo tipo, há vários locais em que o uso de um tipo de referência anulável não é permitido. Em geral, um tipo de referência anulável não pode ser usado como classe base ou interface implementada. Um tipo de referência anulável não pode ser usado nas criações de objeto nem nas expressões de teste de tipo. Um tipo de referência anulável não pode ser o tipo de uma expressão de acesso a membro. Os exemplos a seguir mostram esses constructos:

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ável. Os tipos de referência anulável não são novos tipos de classe, mas sim anotações sobre os tipos de referência existentes. O compilador usa essas anotações para ajudar a encontrar possíveis erros de referência nula no código. Não há diferença de runtime entre um tipo de referência não anulável e um tipo de referência anulável. O compilador não adiciona verificações de runtime para tipos de referência não anulável. Os benefícios estão na análise do tempo de compilação. O compilador gera avisos que ajudam a localizar e corrigir possíveis erros nulos no código. Você declara a intenção e o compilador avisa quando o código violar essa intenção.

Em um contexto habilitado para anulável, o compilador executa uma análise estática nas variáveis de qualquer tipo de referência, anulável e não anulável. 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 anulável. O estado padrão de uma variável de referência anulável é talvez nulo.

Os tipos de referência não anulável devem estar sempre seguros para desreferenciar, pois o estado nulo é não nulo. Para impor essa regra, o compilador emitirá avisos se um tipo de referência não anulável não for inicializado para um valor não nulo. Variáveis locais devem ser atribuídas onde forem declaradas. Cada campo deve receber 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. Em geral, uma referência não anulável é não nula e nenhum aviso é emitido quando essas variáveis são desreferenciadas.

Observação

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

Os tipos de referência anulável podem ser inicializados ou atribuídos a null. Portanto, a análise estática deve determinar se uma variável é não nula, antes que seja desreferenciada. Se uma referência anulável for determinada como talvez nula, a atribuição a uma variável de referência não anulável gerará 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 snippet 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 a 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 poderá saber se esses métodos afetam o estado nulo de uma variável. Existem atributos que você pode adicionar às APIs, para informar o compilador sobre a semântica dos argumentos e os valores retornados. Esses atributos foram aplicados a muitas APIs comuns nas bibliotecas do .NET Core. Por exemplo, IsNullOrEmpty foi atualizado e o compilador interpreta esse método corretamente como verificação nula. Para obter mais informações sobre os atributos que se aplicam à análise estática de estado nulo, confira o artigo sobre Atributos anuláveis.

Definição de contexto anulável

Existem duas maneiras de controlar o contexto anulável. No nível do projeto, você pode adicionar a configuração do projeto <Nullable>enable</Nullable>. Em um único arquivo de origem do C#, você pode adicionar o pragma #nullable enable para habilitar o contexto anulável. Confira o artigo sobre definição de uma estratégia anulável. Antes do .NET 6, os novos projetos usam o padrão, <Nullable>disable</Nullable>. A partir do .NET 6, os novos projetos incluem o elemento <Nullable>enable</Nullable> no arquivo de projeto.

Especificação da linguagem C#

Para obter mais informações, confira as seguintes propostas para a especificação da linguagem C#:

Confira também