Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Tip
Novo em tipos de referência anuláveis? Leia primeiro os tipos de referência anuláveis para compreender as anotações e a análise de estado nulo. Este artigo assume que está a ver avisos num projeto onde a funcionalidade está ativada.
À procura de um código de erro específico do compilador? O artigo de referência Resolver avisos de anulabilidade lista todos os avisos CS86xx e a técnica correspondente.
Quando ativas os tipos de referência anuláveis, o compilador emite avisos em todo o lado onde o comportamento do teu código não corresponde às suas anotações. A maioria dos avisos segue um pequeno conjunto de padrões. Depois de reconhecer o padrão, a solução é geralmente uma de cinco técnicas:
- Adiciona uma verificação de valor nulo.
- Adicione ou remova uma anotação
?ou!. - Adicione um atributo que descreva o contrato nulo.
- Inicializar as variáveis corretamente.
- Verifica a definição do projeto.
Este artigo apresenta cada técnica com um exemplo representativo. O objetivo não é silenciar os avisos. É para tornar explícita a intenção de gestão nula do código, para que o compilador chegue às mesmas conclusões que tu.
Estado de nulidade: o que o compilador controla
Antes de analisar as técnicas, é útil saber como o compilador acompanha potenciais violações do estado nulo. Enquanto lê o seu código, o compilador acompanha o estado nulo de cada expressão: a sua análise sobre se a expressão pode estar null nesse ponto do código. O estado nulo é um de dois valores:
-
not-null — o compilador pode provar que a expressão não é
nullaqui. Pode usá-lo em segurança sem necessidade de verificação. -
Maybe-Null — o compilador não pode excluir
null. Usar a expressão sem verificar produz um aviso.
O estado nulo de uma variável muda à medida que o compilador segue o seu código. Um método que pode devolver null gera um resultado potencialmente nulo. Uma if (x is not null) verificação restringe x a não nulo dentro do bloco if. Os avisos que vês são o compilador a dizer-te que determinou que uma expressão está num estado talvez-nulo e que estás prestes a usá-la como se não fosse nula. Cada técnica no resto deste artigo é uma forma diferente de fornecer ao compilador a informação necessária para garantir que uma expressão não é nula antes de a utilizar.
Adicionar uma verificação nula
O aviso mais comum é a possível desreferenciação de um valor nulo. O compilador rastreava o estado nulo de uma variável até talvez-nulo e via a variável usada sem verificação:
public static int LengthOfMessageUnsafe(string? message)
{
// Warning CS8602: dereference of a possibly null reference.
return message.Length;
}
A solução costuma ser uma cláusula de guarda. Uma cláusula de guarda é uma verificação no topo de um método ou bloco que retorna ou lança quando uma entrada é inválida. Só o caminho seguro continua. Uma vez executada a verificação, 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 de propriedade { Length: > 0 } só corresponde quando message não é nulo e a propriedade Length é superior a zero, pelo que o compilador trata message como não nulo dentro do bloco if. Um teste mais is not null simples produz o mesmo estreitamento do estado nulo sem inspecionar quaisquer propriedades.
Para uma visita detalhada aos operadores, veja Operadores nulos.
Ajustar anotações
O compilador também avisa quando o seu código atribui uma expressão maybe-null a uma variável não anulável. Esse aviso significa uma de duas coisas:
- A variável deve permitir valores nulos. Nesse caso, adiciona um
?ao tipo. - A expressão nunca produz um valor nulo. Anota a API que o 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 devolve legitimamente null, altere o site da chamada para aceitar o valor em falta:
public static void AssignmentFixed()
{
string? name = Lookup("somebody");
if (name is not null)
{
Console.WriteLine(name);
}
}
Se Lookup nunca devolver nulo, altere a assinatura para devolver um tipo de referência não anulável. Cenários em que o estado nulo do valor devolvido depende da entrada, veja a secção seguinte sobre atributos de análise nula.
Utilize o operador de supressão de null ! apenas quando puder garantir que um valor não é null, mas não conseguir expressar essa garantia no sistema de tipos. Cada ! é um ponto em que o compilador já não te pode proteger, por isso, é preferível adicionar uma verificação ou anotar a API de origem.
Adicionar um atributo de análise nula
Por vezes, a solução certa não está no local da chamada. A assinatura de um método não capta a relação entre as suas entradas e saídas com precisão suficiente, e o compilador emite avisos dentro de código que seria de outra forma 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 o indica.
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);
}
}
Atributos comuns incluem:
- NotNullWhenAttribute — o argumento não é nulo quando o método retorna o Booleano especificado.
- NotNullIfNotNullAttribute — o valor de retorno é não-nulo sempre que o argumento nomeado não é nulo.
- MemberNotNullAttribute — os membros listados não são nulos após o retorno do método.
- DoesNotReturnAttribute — o método nunca retorna normalmente (por exemplo, lança sempre).
A lista completa encontra-se em atributos de análise estática anulável.
Inicializar membros não anuláveis
Um aviso de construtor significa que um campo, propriedade ou auto-propriedade não anulável (uma propriedade que utiliza o campo de suporte gerado pelo compilador, como public string Name { get; set; }) sai do construtor sem lhe ser atribuído um valor não nulo:
public class PersonUninitialized
{
// Warning CS8618: Non-nullable property 'Name' is uninitialized.
public string Name { get; set; }
}
Tens várias formas de lidar com isso. Escolhe aquele que melhor se adequa à tua intenção de design.
Requer o valor como argumento construtor. Use um construtor primário (parâmetros declarados no próprio tipo, disponíveis em todo o corpo) ou um construtor regular que inicializa a propriedade:
public class PersonInjected(string name)
{
public string Name { get; } = name;
}
Defina a propriedade como required. O chamador deve inicializá-lo recorrendo a um inicializador de objetos (a sintaxe { Property = value } que se segue a new):
public class PersonRequired
{
public required string Name { get; init; }
}
Inicializar com um valor predefinido. Quando o tipo tem um valor vazio significativo, inicialize na declaração:
public class PersonInitialized
{
public string Name { get; set; } = "John Doe";
}
Tip
Escolha esta técnica apenas quando o tipo tiver um valor predefinido verdadeiramente bom: um valor que seja uma instância válida e totalmente funcional, que o código chamador possa utilizar. Exemplos incluem coleções vazias. Não inventes um valor sentinela (um valor de substituição, como String.Empty, "N/A", "unknown" ou -1, que tratas como "sem valor") em vez de null: isso silencia o aviso, mas todos os chamadores têm de conhecer e verificar esse valor sentinela, e o sistema de tipos não pode ajudar. Quando não existir um bom incumprimento, torne a propriedade anulável em vez disso.
Torna a propriedade anulável. Quando o valor realmente estiver em falta, altere o tipo para nullable:
public class PersonOptional
{
public string? Name { get; set; }
}
Se um método auxiliar inicializar o membro, anote o método auxiliar com MemberNotNullAttribute para que o compilador possa atribuir-lhe as chamadas.
Verificar a definição do projeto
Novos projetos em C# permitem por defeito tipos de referência anuláveis, por isso a maior parte do código que escreves ou lês já tem a funcionalidade ativada. Geralmente, não precisas de configurar nada. Se tens curiosidade em saber se um projeto tem isso ativado, ou se precisas de mudar a definição, procura o <Nullable> elemento no .csproj:
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
Os valores suportados comuns são enable (o padrão para novos projetos) e disable. Se o elemento estiver em falta, o projeto usa o que o SDK e o framework alvo definiram por padrão.
Se precisar de ativar a anulabilidade apenas numa parte de um ficheiro com diretivas #nullable, ou de usar os modos parciais warnings e annotations ao migrar código existente, consulte Estratégias de migração para anulabilidade.
Onde ir a seguir
Quando um aviso não se enquadra em nenhum destes padrões, o artigo de referência sobre avisos anuláveis do Resolve lista a técnica para cada aviso CS86xx emitido pelo compilador.
Para planear uma migração que permita progressivamente tipos de referência anuláveis numa base de código existente, veja Estratégias de migração anuláveis.