Resolver avisos anuláveis
Este artigo aborda os seguintes avisos do compilador:
- CS8597 - Valor gerado pode ser nulo.
- CS8600 - Convertendo literal nulo ou possível valor nulo em tipo não anulável.
- CS8601 - Possível atribuição de referência nula.
- CS8602 - Desreferência de uma referência possivelmente nula.
- CS8603 - Possível retorno de referência nula.
- CS8604 - Possível argumento de referência nula para parâmetro.
- CS8605 - Conversão unboxing de um valor possivelmente nulo.
- CS8607 - Um possível valor nulo pode não ser usado para um tipo marcado com
[NotNull]
ou[DisallowNull]
- CS8608 - A nulidade de tipos de referência no tipo não corresponde ao membro substituído.
- CS8609 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro substituído.
- CS8610 - A nulidade de tipos de referência no parâmetro de tipo não corresponde ao membro substituído.
- CS8611 - A nulidade de tipos de referência em tipo de parâmetro não corresponde à declaração de método parcial.
- CS8612 - A nulidade de tipos de referência no tipo não corresponde ao membro implementado implicitamente.
- CS8613 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente.
- CS8614 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado implicitamente.
- CS8615 - A nulidade de tipos de referência no tipo não corresponde ao membro implementado.
- CS8616 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado.
- CS8617 - A anulabilidade de tipos de referência em tipo de parâmetro não corresponde ao membro implementado.
- CS8618 - Variável não anulável deve conter um valor não nulo ao sair do construtor. Considere declará-lo como anulável.
- CS8619 - A nulidade de tipos de referência no valor não corresponde ao tipo de destino.
- CS8620 - O argumento não pode ser usado para o parâmetro devido a diferenças na nulidade dos tipos de referência.
- CS8621 - A nulidade de tipos de referência no tipo de retorno não corresponde ao delegado de destino (possivelmente devido a atributos de nulidade).
- CS8622 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao delegado de destino (possivelmente devido a atributos de nulidade).
- CS8624 - O argumento não pode ser usado como uma saída devido a diferenças na nulidade dos tipos de referência.
- CS8625 - Não é possível converter literal nulo em tipo de referência não anulável.
- CS8629 - tipo de valor anulável pode ser nulo.
- CS8631 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A nulidade do argumento de tipo não corresponde ao tipo de restrição.
- CS8633 - A nulidade em restrições para o parâmetro de tipo do método não corresponde às restrições para o parâmetro de tipo do método de interface. Em vez disso, considere usar uma implementação de interface explícita.
- CS8634 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A nulidade do argumento de tipo não corresponde à restrição 'class'.
- CS8643 - A nulidade dos tipos de referência no especificador de interface explícito não corresponde à interface implementada pelo tipo.
- CS8644 - O tipo não implementa o membro da interface. A nulidade de tipos de referência na interface implementada pelo tipo base não corresponde.
- CS8645 - O membro já está listado na lista de interfaces no tipo com nulidade diferente de tipos de referência.
- CS8655 - A expressão switch não manipula algumas entradas nulas (ela não é finita).
- CS8667 - Declarações de método parcial têm nulidade inconsistente em restrições para o parâmetro de tipo.
- CS8670 - Objeto ou inicializador de coleção desreferencia implicitamente possivelmente membro nulo.
- CS8714 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A nulidade do argumento de tipo não corresponde à restrição 'notnull'.
- CS8762 - O parâmetro deve ter um valor não nulo ao sair.
- O método CS8763 - A marcado como
[DoesNotReturn]
não deve retornar. - CS8764 - A nulidade do tipo de retorno não corresponde ao membro substituído (possivelmente devido a atributos de nulidade).
- CS8765 - A nulidade do tipo de parâmetro não corresponde ao membro substituído (possivelmente devido a atributos de nulidade).
- CS8766 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente (possivelmente devido a atributos de nulidade).
- CS8767 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado implicitamente (possivelmente devido a atributos de nulidade).
- CS8768 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado (possivelmente devido a atributos de nulidade).
- CS8769 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado (possivelmente devido a atributos de nulidade).
- CS8770 - O método não tem a anotação
[DoesNotReturn]
para corresponder ao membro implementado ou substituído. - CS8774 - O membro deve ter um valor não nulo ao sair.
- CS8776 - O membro não pode ser usado nesse atributo.
- CS8775 - O membro deve ter um valor não nulo ao sair.
- CS8777 - O parâmetro deve ter um valor não nulo ao sair.
- CS8819 - A nulidade de tipos de referência no tipo de retorno não corresponde à declaração de método parcial.
- CS8824 - O parâmetro deve ter um valor não nulo ao sair porque o parâmetro não é nulo.
- CS8825 - O valor retornado precisa ser não nulo porque o parâmetro não é nulo.
- CS8847 - A expressão switch não manipula algumas entradas nulas (não é exaustiva). No entanto, um padrão com uma cláusula 'when' pode corresponder com êxito a esse valor.
A finalidade dos avisos anuláveis é minimizar a chance do aplicativo gerar System.NullReferenceException quando executado. Para atingir essa meta, o compilador usa análise estática e emite avisos quando o código tem constructos que podem gerar exceções de referência nulas. Você fornece ao compilador informações para análise estática aplicando anotações de tipo e atributos. Essas anotações e atributos descrevem a nulidade de argumentos, parâmetros e membros de seus tipos. Neste artigo, você aprenderá diferentes técnicas para abordar os avisos anuláveis gerados pelo compilador a partir de sua análise estática. As técnicas descritas aqui são para o código C# geral. Saiba como trabalhar com tipos de referência anuláveis e o Entity Framework Core em Trabalhar com tipos de referência anuláveis.
Quase todos os avisos serão abordados usando uma das quatro técnicas:
- Adicionando verificações nulas necessárias.
- Adicionando anotações anuláveis
?
ou!
. - Adicionando atributos que descrevem a semântica nula.
- Inicializando variáveis corretamente.
Possível desreferência do nulo
Esse conjunto de avisos alerta que você está desreferenciando uma variável cujo estado nulo é talvez nulo. Estes avisos são:
- CS8602 - Desreferência de uma referência possivelmente nula.
- CS8670 - Objeto ou inicializador de coleção desreferencia implicitamente possivelmente membro nulo.
O seguinte código demonstra um exemplo de cada um dos avisos anteriores:
class Container
{
public List<string>? States { get; set; }
}
internal void PossibleDereferenceNullExamples(string? message)
{
Console.WriteLine(message.Length); // CS8602
var c = new Container { States = { "Red", "Yellow", "Green" } }; // CS8670
}
No exemplo acima, o aviso ocorre porque o Container
, c
, pode ter um valor nulo para a propriedade States
. Atribuir novos estados a uma coleção que pode ser nula causa o aviso.
Para remover esses avisos, é necessário adicionar código para alterar o estado nulo dessa variável para não nulo antes de desreferenciá-la. O aviso do inicializador de coleção pode ser mais difícil de detectar. O compilador detecta que a coleção talvez seja nula quando o inicializador adiciona elementos a ela.
Em muitas instâncias, você pode corrigir esses avisos verificando se uma variável não é nula antes de desreferenciá-la. Considere o seguinte exemplo, que adiciona uma verificação de nulo antes de fazer referência ao parâmetro message
:
void WriteMessageLength(string? message)
{
if (message is not null)
{
Console.WriteLine(message.Length);
}
}
O exemplo a seguir inicializa o armazenamento com suporte para States
e remove o acessador set
. Os consumidores da classe podem modificar o conteúdo da coleção, e o armazenamento da coleção nunca é null
:
class Container
{
public List<string> States { get; } = new();
}
Outras instâncias ao receber esses avisos podem ser falsas positivas. Você pode ter um método utilitário privado que testa nulo. O compilador não sabe que o método fornece uma verificação nula. Considere o exemplo a seguir que usa um método utilitário privado IsNotNull
:
public void WriteMessage(string? message)
{
if (IsNotNull(message))
Console.WriteLine(message.Length);
}
O compilador avisa que você pode estar desreferenciando o nulo ao gravar a propriedade message.Length
porque sua análise estática determina que message
pode ser null
. Você pode saber que IsNotNull
fornece uma verificação nula e, quando retorna true
, o estado nulo de message
deve ser não nulo. Você deve informar esses fatos ao compilador. Uma maneira é usar o operador tolerante a nulo !
. Você pode alterar a instrução WriteLine
para corresponder ao seguinte código:
Console.WriteLine(message!.Length);
O operador tolerante a nulo torna a expressão não nula ainda que ela fosse talvez nula sem o !
aplicado. Neste exemplo, uma solução melhor é a inclusão de um atributo à assinatura de IsNotNull
:
private static bool IsNotNull([NotNullWhen(true)] object? obj) => obj != null;
System.Diagnostics.CodeAnalysis.NotNullWhenAttribute informa ao compilador que o argumento usado para o parâmetro obj
é não nulo quando o método retorna true
. Quando o método retorna false
, o argumento tem o mesmo estado nulo que tinha antes do método ser chamado.
Dica
Há um conjunto avançado de atributos que podem ser usados para descrever como seus métodos e propriedades afetam o estado nulo. Você pode aprender sobre eles no artigo de referência de idioma em Análise estática que permite valor nulo.
A correção de um aviso para desreferenciar uma variável talvez nula envolve uma das três técnicas:
- Adicionar uma verificação nula ausente.
- Adicione atributos de análise nulos em APIs para afetar a análise estática do estado nulo do compilador. Esses atributos informam o compilador quando um valor ou argumento retornado deve ser talvez nulo ou não nulo depois de chamar o método.
- Aplique o operador tolerante a nulo
!
à expressão para forçar o estado para não nulo.
Possível nulo atribuído a uma referência não anulável
Esse conjunto de avisos alerta que você está atribuindo uma variável cujo tipo não pode ser nulo a uma expressão cujo estado nulo é talvez nulo. Estes avisos são:
- CS8597 - Valor gerado pode ser nulo.
- CS8600 - Convertendo literal nulo ou possível valor nulo em tipo não anulável.
- CS8601 - Possível atribuição de referência nula.
- CS8603 - Possível retorno de referência nula.
- CS8604 - Possível argumento de referência nula para parâmetro.
- CS8605 - Conversão unboxing de um valor possivelmente nulo.
- CS8625 - Não é possível converter literal nulo em tipo de referência não anulável.
- CS8629 - O tipo de valor anulável pode ser nulo.
O compilador emite esses avisos quando você tenta atribuir uma expressão talvez nula a uma variável não anulável. Por exemplo:
string? TryGetMessage(int id) => "";
string msg = TryGetMessage(42); // Possible null assignment.
Os avisos diferentes fornecem detalhes sobre o código, como atribuição, atribuição de unboxing, instruções de retorno, argumentos para métodos e gerar expressões.
Você pode executar uma das três ações para resolver esses avisos. Uma delas é adicionar a anotação ?
para tornar a variável um tipo de referência anulável. Essa alteração pode gerar outros avisos. Alterar uma variável de uma referência não anulável para uma referência anulável altera o estado nulo padrão de não nulo para talvez nulo. A análise estática do compilador pode encontrar instâncias em que você desreferencia uma variável talvez nula.
As outras ações informam ao compilador que o lado direito da atribuição é não nulo. A expressão no lado direito pode ser marcada como nula antes da atribuição, conforme mostrado no exemplo a seguir:
string notNullMsg = TryGetMessage(42) ?? "Unknown message id: 42";
Os exemplos anteriores demonstram a atribuição ao valor retornado de um método. Você pode anotar o método (ou propriedade) para indicar quando um método retorna um valor não nulo. Geralmente System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute especifica que um valor retornado é não nulo quando um argumento de entrada é não nulo. Uma alternativa é adicionar o operador tolerante a nulo!
ao lado direito:
string msg = TryGetMessage(42)!;
A correção de um aviso para atribuir uma expressão talvez nula a uma variável não nula envolve uma das quatro técnicas:
- Alterar o lado esquerdo da atribuição para um tipo anulável. Essa ação pode introduzir novos avisos ao desreferenciar essa variável.
- Fornecer uma verificação nula antes da atribuição.
- Anotar a API que produz o lado direito da atribuição.
- Adicionar o operador tolerante a nulo ao lado direito da atribuição.
Referência não anulável não inicializada
Esse conjunto de avisos alerta que você está atribuindo uma variável cujo tipo não pode ser nulo a uma expressão cujo estado nulo é talvez nulo. Estes avisos são:
- CS8618 - Variável não anulável deve conter um valor não nulo ao sair do construtor. Considere declará-lo como anulável.
- CS8762 - O parâmetro deve ter um valor não nulo ao sair.
Considere a seguinte classe a título de exemplo:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
FirstName
ou LastName
não são garantidos inicializados. Se o código for novo, considere alterar a interface pública. O exemplo acima pode ser atualizado da seguinte maneira:
public class Person
{
public Person(string first, string last)
{
FirstName = first;
LastName = last;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
Caso seja necessário criar um objeto Person
antes de definir o nome, será possível inicializar as propriedades usando um valor não nulo padrão:
public class Person
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
}
Uma alternativa pode ser alterar esses membros para tipos de referência anuláveis. A classe Person
poderá ser definida da seguinte maneira se null
for permitido para o nome:
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
O código existente pode exigir outras alterações para informar o compilador sobre a semântica nula para esses membros. Você pode ter criado vários construtores e sua classe pode ter um método auxiliar privado que inicializa um ou mais membros. Você pode mover o código de inicialização para um único construtor e garantir que todos os construtores chamem aquele com o código de inicialização comum. Ou você pode usar os atributos System.Diagnostics.CodeAnalysis.MemberNotNullAttribute e System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute. Esses atributos informam ao compilador que um membro é não nulo depois que o método é chamado. O código a seguir mostra um exemplo de cada um desses casos. A classe Person
usa um construtor comum chamado por todos os outros construtores. A classe Student
tem um método auxiliar anotado com o atributo System.Diagnostics.CodeAnalysis.MemberNotNullAttribute:
using System.Diagnostics.CodeAnalysis;
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public Person() : this("John", "Doe") { }
}
public class Student : Person
{
public string Major { get; set; }
public Student(string firstName, string lastName, string major)
: base(firstName, lastName)
{
SetMajor(major);
}
public Student(string firstName, string lastName) :
base(firstName, lastName)
{
SetMajor();
}
public Student()
{
SetMajor();
}
[MemberNotNull(nameof(Major))]
private void SetMajor(string? major = default)
{
Major = major ?? "Undeclared";
}
}
Por fim, você pode usar o operador tolerante a nulo para indicar que um membro é inicializado em outro código. Para obter outro exemplo, considere as seguintes classes que representam um modelo Entity Framework Core:
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; } = null!;
}
A propriedade DbSet
é inicializada com null!
. Isso informa ao compilador que a propriedade foi definida como um valor não nulo. Na verdade, a base DbContext
executa a inicialização do conjunto. A análise estática do compilador não capta isso. Para obter mais informações sobre como trabalhar com tipos de referência anuláveis e o Entity Framework Core, consulte o artigo sobre Como trabalhar com Tipos de Referência Anuláveis no EF Core.
A correção de um aviso para não inicializar um membro não anulável envolve uma das quatro técnicas:
- Alterar os construtores ou inicializadores de campo para garantir que todos os membros não anuláveis sejam inicializados.
- Alterar um ou mais membros para serem tipos anuláveis.
- Anotar quaisquer métodos auxiliares para indicar quais membros são atribuídos.
- Adicione um inicializador para
null!
indicar que o membro é inicializado em outro código.
Incompatibilidade na declaração de nulidade
Muitos avisos indicam incompatibilidades de nulidade entre assinaturas de métodos, delegados ou parâmetros de tipo.
- CS8608 - A nulidade de tipos de referência no tipo não corresponde ao membro substituído.
- CS8609 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro substituído.
- CS8610 - A nulidade de tipos de referência no parâmetro de tipo não corresponde ao membro substituído.
- CS8611 - A nulidade de tipos de referência em tipo de parâmetro não corresponde à declaração de método parcial.
- CS8612 - A nulidade de tipos de referência no tipo não corresponde ao membro implementado implicitamente.
- CS8613 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente.
- CS8614 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado implicitamente.
- CS8615 - A nulidade de tipos de referência no tipo não corresponde ao membro implementado.
- CS8616 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado.
- CS8617 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado.
- CS8619 - A nulidade de tipos de referência no valor não corresponde ao tipo de destino.
- CS8620 - O argumento não pode ser usado para o parâmetro devido a diferenças na nulidade dos tipos de referência.
- CS8621 - A nulidade de tipos de referência no tipo de retorno não corresponde ao delegado de destino (possivelmente devido a atributos de nulidade).
- CS8622 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao delegado de destino (possivelmente devido a atributos de nulidade).
- CS8624 - O argumento não pode ser usado como uma saída devido a diferenças na nulidade dos tipos de referência.
- CS8631 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A nulidade do argumento de tipo não corresponde ao tipo de restrição.
- CS8633 - A nulidade em restrições para o parâmetro de tipo do método não corresponde às restrições para o parâmetro de tipo do método de interface. Em vez disso, considere usar uma implementação de interface explícita.
- CS8634 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A nulidade do argumento de tipo não corresponde à restrição 'class'.
- CS8643 - A nulidade dos tipos de referência no especificador de interface explícito não corresponde à interface implementada pelo tipo.
- CS8644 - O tipo não implementa o membro da interface. A nulidade de tipos de referência na interface implementada pelo tipo base não corresponde.
- CS8645 - O membro já está listado na lista de interfaces no tipo com nulidade diferente de tipos de referência.
- CS8667 - Declarações de método parcial têm nulidade inconsistente em restrições para o parâmetro de tipo.
- CS8714 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A nulidade do argumento de tipo não corresponde à restrição 'notnull'.
- CS8764 - A nulidade do tipo de retorno não corresponde ao membro substituído (possivelmente devido a atributos de nulidade).
- CS8765 - A nulidade do tipo de parâmetro não corresponde ao membro substituído (possivelmente devido a atributos de nulidade).
- CS8766 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente (possivelmente devido a atributos de nulidade).
- CS8767 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado implicitamente (possivelmente devido a atributos de nulidade).
- CS8768 - A nulidade de tipos de referência no tipo de retorno não corresponde ao membro implementado (possivelmente devido a atributos de nulidade).
- CS8769 - A nulidade de tipos de referência no tipo de parâmetro não corresponde ao membro implementado (possivelmente devido a atributos de nulidade).
- CS8819 - A nulidade de tipos de referência no tipo de retorno não corresponde à declaração de método parcial.
O seguinte código demonstra o erro CS8764:
public class B
{
public virtual string GetMessage(string id) => string.Empty;
}
public class D : B
{
public override string? GetMessage(string? id) => default;
}
O exemplo anterior mostra um método virtual
em uma classe base e um override
com nulidade diferente. A classe base retorna uma cadeia de caracteres não anulável, mas a classe derivada retorna uma cadeia de caracteres anulável. Se string
e string?
forem invertidos, ele será permitido porque a classe derivada é mais restritiva. Da mesma forma, as declarações de parâmetro devem corresponder. Os parâmetros no método de substituição podem permitir o nulo mesmo quando a classe base não permite.
Outras situações podem gerar esses avisos. Você pode ter uma incompatibilidade em uma declaração de método de interface e na implementação desse método. Ou um tipo de delegado e a expressão desse delegado podem ser diferentes. Um parâmetro de tipo e o argumento de tipo podem diferir na nulidade.
Para corrigir esses avisos, atualize a declaração apropriada.
O código não corresponde à declaração de atributo
As seções anteriores discutiram como você pode usar os Atributos para análise estática anulável para informar o compilador sobre a semântica nula do código. O compilador avisará se o código não cumprir as promessas desse atributo:
- CS8607 - Um possível valor nulo pode não ser usado para um tipo marcado com
[NotNull]
ou[DisallowNull]
- O método CS8763 - A marcado como
[DoesNotReturn]
não deve retornar. - CS8770 - O método não tem a anotação
[DoesNotReturn]
para corresponder ao membro implementado ou substituído. - CS8774 - O membro deve ter um valor não nulo ao sair.
- CS8775 - O membro deve ter um valor não nulo ao sair.
- CS8776 - O membro não pode ser usado nesse atributo.
- CS8777 - O parâmetro deve ter um valor não nulo ao sair.
- CS8824 - O parâmetro deve ter um valor não nulo ao sair porque o parâmetro não é nulo.
- CS8825 - O valor retornado precisa ser não nulo porque o parâmetro não é nulo.
Considere o método a seguir:
public bool TryGetMessage(int id, [NotNullWhen(true)] out string? message)
{
message = null;
return true;
}
O compilador produz um aviso porque o parâmetro message
é atribuído null
e o método retorna true
. O atributo NotNullWhen
indica que isso não deve acontecer.
Para resolver esses avisos, atualize o código para que ele corresponda às expectativas dos atributos aplicados. É possível alterar os atributos ou o algoritmo.
Expressões de switch exaustivas
As expressões de comutador devem ser exaustivas, o que significa que todos os valores de entrada devem ser tratados. Mesmo para tipos de referência não anuláveis, o valor null
deve ser contabilizado. O compilador emite avisos quando o valor nulo não é tratado:
- CS8655 - A expressão switch não manipula algumas entradas nulas (ela não é finita).
- CS8847 - A expressão switch não manipula algumas entradas nulas (não é exaustiva). No entanto, um padrão com uma cláusula 'when' pode corresponder com êxito a esse valor.
O código de exemplo a seguir demonstra essa condição:
int AsScale(string status) =>
status switch
{
"Red" => 0,
"Yellow" => 5,
"Green" => 10,
{ } => -1
};
A expressão de entrada é um string
, não um string?
. O compilador ainda gera esse aviso. O padrão { }
manipula todos os valores não nulos, mas não corresponde a null
. Para resolver esses erros, você pode adicionar um caso de null
explícito ou substituir o { }
pelo padrão _
(descarte). O padrão de descarte corresponde a nulo, bem como a qualquer outro valor.