Partilhar via


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.

Os tipos de referência anuláveis estão disponíveis no código que está em um contexto de reconhecimento nulo . 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 são desativados por padrão. Um contexto anulável é controlado no nível do projeto usando configurações de compilação ou em código usando pragmas.

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:

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

O compilador impõe as distinções entre um tipo de referência não anulável T e um tipo de referência anulável T? 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 são ambas representadas pelo String tipo. Como os tipos não anulável e anulável são armazenados como o mesmo tipo, há vários locais onde 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 uma classe base ou interface implementada. Um tipo de referência anulável não pode ser usado em nenhuma criação de objeto ou expressão de teste de tipo. Um tipo de referência anulável não pode ser o tipo de uma expressão de acesso de 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.

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. As variáveis locais devem ser atribuídas onde são declaradas. 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 nenhum aviso é emitido quando essas variáveis são desreferenciadas.

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 .

Os tipos de referência anuláveis podem ser inicializados ou atribuídos a null. 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, 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 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. Há atributos que você pode adicionar às suas APIs para informar o compilador sobre a semântica de 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.

Definindo o contexto anulável

Há duas maneiras de controlar o contexto anulável. No nível do projeto, você pode adicionar a configuração do <Nullable>enable</Nullable> projeto. Em um único arquivo de origem C#, você pode adicionar o #nullable enable pragma para habilitar o contexto anulável. Consulte o artigo sobre como 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.

Especificação da linguagem C#

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

Consulte também