Corrigir avisos de nulabilidade

Dica

Não conhece tipos de referência anuláveis? Leia primeiro Tipos de referência anuláveis para entender as anotações e a análise de estado de nulidade. Este artigo pressupõe que você esteja vendo avisos em um projeto em que o recurso está habilitado.

Procurando um código de erro específico do compilador? O artigo de referência Resolver avisos de anulabilidade cataloga cada aviso CS86xx com a técnica correspondente.

Quando você habilita tipos de referência que permitem valor nulo, o compilador emite avisos em todos os lugares em que o comportamento do código não corresponde às anotações. A maioria dos avisos se enquadra em um pequeno conjunto de padrões. Depois de reconhecer o padrão, a correção geralmente é uma das cinco técnicas:

  • Adicione uma verificação nula.
  • Adicionar ou remover uma anotação ? ou !.
  • Adicione um atributo que descreve o contrato nulo.
  • Inicializar variáveis corretamente.
  • Verifique a configuração do projeto.

Este artigo explica cada técnica com um exemplo representativo. O objetivo não é silenciar avisos. É para tornar a intenção de tratamento nulo do código explícita para que o compilador chegue às mesmas conclusões que você.

Estado nulo: o que o compilador rastreia

Antes de examinar as técnicas, é útil saber como o compilador rastreia possíveis violações relacionadas ao estado de nulidade. Ao ler seu código, o compilador controla o estado nulo de cada expressão: sua análise sobre se a expressão pode estar null nesse ponto no código. O estado nulo é um dos dois valores:

  • not-null — o compilador pode provar que a expressão não é null aqui. Você pode usá-lo com segurança sem uma verificação.
  • maybe-null – o compilador não pode descartar null. Usar a expressão sem verificá-la gera um aviso.

O estado nulo de uma variável é alterado conforme o compilador segue seu código. Um método que pode retornar null produz um resultado talvez nulo . Uma verificação if (x is not null) restringe x a não nulo dentro do bloco if. Os avisos que você vê são o compilador informando que ele determinou que uma expressão está em um estado talvez nulo e você está prestes a usá-la como se ela não fosse nula. Cada técnica no restante deste artigo é uma maneira diferente de fornecer ao compilador as informações necessárias para garantir que uma expressão não seja nula antes de usá-la .

Adicionar uma verificação nula

O aviso mais comum é possível desreferência de nulo. O compilador rastreou o estado nulo de uma variável como talvez nulo e viu a variável usada sem uma verificação:

public static int LengthOfMessageUnsafe(string? message)
{
    // Warning CS8602: dereference of a possibly null reference.
    return message.Length;
}

A correção geralmente é uma cláusula de proteção. Uma cláusula de proteção é uma verificação na parte superior de um método ou bloco que retorna ou lança quando uma entrada é inválida. Somente o caminho seguro continua. Depois que a verificação é executada, o compilador atualiza o estado nulo da variável para não nulo no caminho seguro:

public static int DereferenceFixed(string? message)
{
    if (message is null)
    {
        return 0;
    }

    // No warning: the compiler knows message is not-null on this path.
    return message.Length;
}

Correspondência de padrões (expressões como is null ou is { } que testam a forma de um valor) ??e ??= incluem verificações nulas:

public static int NullOperatorsFix(string? message)
{
    // ?. evaluates to null if message is null; ?? supplies the fallback value.
    int length = message?.Length ?? 0;

    // Pattern matching narrows the type on the matching branch.
    if (message is { Length: > 0 })
    {
        length = message.Length;
    }

    return length;
}

O padrão { Length: > 0 } de propriedade corresponde somente quando message não é nulo e sua Length propriedade é maior que zero, portanto, o compilador trata message como não nulo dentro do if bloco. Um teste mais simples is not null produz o mesmo estreitamento do valor nulo sem inspecionar quaisquer propriedades.

Para uma visão detalhada dos operadores, consulte Operadores nulos.

Ajustar anotações

O compilador também avisa quando seu código atribui uma expressão talvez nula a uma variável não anulável. Esse aviso significa uma das duas coisas:

  • A variável deve permitir valores nulos. Nesse caso, adicione um ? ao tipo.
  • A expressão nunca produz um valor nulo. Anotar a API que a produziu.
public static void AssignmentWarning()
{
    // Warning CS8600: converting null literal or possible null value to non-nullable type.
    string name = Lookup("nobody");
    Console.WriteLine(name);
}

Se Lookup legitimamente retornar nulo, altere o site de chamada para aceitar o valor ausente:

public static void AssignmentFixed()
{
    string? name = Lookup("somebody");
    if (name is not null)
    {
        Console.WriteLine(name);
    }
}

Se Lookup nunca retornar nulo, altere sua assinatura para retornar um tipo de referência não anulável. Cenários em que o estado nulo do valor retornado depende da entrada, consulte a seção a seguir em atributos de análise nula.

Use o operador de supressão de null ! somente quando você puder garantir que um valor não é nulo, mas não puder expressar essa garantia no sistema de tipos. Cada ! é um ponto em que o compilador não pode mais proteger você, portanto prefira adicionar uma verificação ou adicionar anotações à API de origem.

Adicionar um atributo de análise nula

Às vezes, a correção certa não está no local da chamada. A assinatura de um método não captura com precisão suficiente a relação entre suas entradas e saídas, e o compilador emite avisos em um código que, de outro modo, seria seguro:

public static bool IsPresent(string? text) =>
    !string.IsNullOrEmpty(text);

public static void CallerWithoutAttribute(string? text)
{
    if (IsPresent(text))
    {
        // Warning CS8602: dereference of a possibly null reference.
        // The signature doesn't tell the compiler text is not-null here.
        Console.WriteLine(text.Length);
    }
}

O corpo de IsPresent prova que o argumento não é nulo quando o método retorna true, mas a assinatura não diz isso. Adicione um atributo de análise anulável para tornar o contrato parte da API:

public static bool AttributedIsPresent([NotNullWhen(true)] string? text) =>
    !string.IsNullOrEmpty(text);

public static void CallerWithAttribute(string? text)
{
    if (AttributedIsPresent(text))
    {
        // No warning: the attribute tells the compiler text is not-null.
        Console.WriteLine(text.Length);
    }
}

Os atributos comuns incluem:

A lista completa está em atributos anuláveis de análise estática.

Inicializar membros não anuláveis

Um aviso de construtor significa que um campo, uma propriedade ou uma propriedade automática não anulável (uma propriedade que usa o campo de apoio gerado pelo compilador, como public string Name { get; set; }) deixa o construtor sem receber um valor não nulo:

public class PersonUninitialized
{
    // Warning CS8618: Non-nullable property 'Name' is uninitialized.
    public string Name { get; set; }
}

Você tem várias maneiras de resolver isso. Escolha aquele que melhor corresponda à sua intenção de design.

Exija o valor como um argumento de construtor. Use um construtor primário (parâmetros declarados no próprio tipo, disponíveis em todo o corpo) ou um construtor regular que inicialize a propriedade:

public class PersonInjected(string name)
{
    public string Name { get; } = name;
}

Defina a propriedade required. O chamador deve inicializá-lo usando um inicializador de objeto (a sintaxe { Property = value } a seguir a new):

public class PersonRequired
{
    public required string Name { get; init; }
}

Inicie com um valor padrão. Quando o tipo tiver um valor vazio significativo, inicialize na declaração:

public class PersonInitialized
{
    public string Name { get; set; } = "John Doe";
}

Dica

Escolha essa técnica somente quando o tipo tiver um valor padrão realmente adequado: isto é, uma instância válida e totalmente funcional para os chamadores usarem. Exemplos incluem coleções vazias. Não invente um valor sentinela (um valor de preenchimento, como String.Empty, "N/A", "unknown" ou -1, que você trata como "sem valor") para substituir null: isso suprime o aviso, mas cada chamador precisa conhecer e verificar esse valor sentinela, e o sistema de tipos não pode ajudar. Quando não houver um valor padrão adequado, faça com que a propriedade aceite valor nulo em vez disso.

Torne a propriedade anulável. Quando o valor realmente estiver ausente, altere o tipo para anulável:

public class PersonOptional
{
    public string? Name { get; set; }
}

Se um método auxiliar inicializa o membro, anote o método auxiliar com MemberNotNullAttribute para que o compilador possa atribuir a ele as chamadas.

Verificar a configuração do projeto

Novos projetos em C# habilitam tipos de referência anuláveis por padrão, portanto, a maioria dos códigos que você escreve ou lê já tem o recurso ativado. Geralmente, você não precisa configurar nada. Se você estiver curioso se um projeto o habilitou ou se precisa alterar a configuração, procure o <Nullable> elemento no .csproj:

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

Os valores comuns com suporte são enable (o padrão para novos projetos) e disable. Se o elemento estiver ausente, o projeto usará qualquer padrão do SDK e do conjunto de estruturas de destino.

Se você precisar habilitar nullable em apenas parte de um arquivo com as diretivas #nullable, ou usar os modos parciais warnings e annotations ao migrar uma base de código existente, consulte Estratégias de migração de nullable.

Para onde ir

Quando um aviso não se ajusta a nenhum desses padrões, o artigo de referência Resolver avisos anuláveis lista a técnica para cada aviso CS86xx que o compilador emite.

Para planejar uma migração que habilite progressivamente tipos de referência anuláveis em uma base de código existente, consulte estratégias de migração anuláveis.