Resolver avisos anuláveis
Este artigo aborda os seguintes avisos do compilador:
- CS8597 - Valor lançado pode ser nulo.
- CS8600 - Convertendo literal nulo ou possível valor nulo para 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 nulo.
- CS8604 - Possível argumento de referência nula para parâmetro.
- CS8605 - Unboxing um valor possivelmente nulo.
- CS8607 - Um possível valor nulo não pode ser usado para um tipo marcado com
[NotNull]
ou[DisallowNull]
- CS8608 - A anulabilidade dos tipos de referência no tipo não corresponde ao membro substituído.
- CS8609 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro substituído.
- CS8610 - A anulabilidade dos tipos de referência no parâmetro type não corresponde ao membro substituído.
- CS8611 - A anulabilidade dos tipos de referência no parâmetro type não corresponde à declaração parcial do método.
- CS8612 - A anulabilidade dos tipos de referência no tipo não corresponde ao membro implementado implicitamente.
- CS8613 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente.
- CS8614 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao membro implementado implicitamente.
- CS8615 - A anulabilidade dos tipos de referência no tipo não corresponde ao membro implementado.
- CS8616 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado.
- CS8617 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao membro implementado.
- CS8618 - A 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 anulabilidade dos tipos de referência no valor não corresponde ao tipo de destino.
- O argumento CS8620 - não pode ser usado para parâmetro devido a diferenças na anulabilidade dos tipos de referência.
- CS8621 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao delegado de destino (possivelmente devido a atributos de anulabilidade).
- CS8622 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao delegado de destino (possivelmente devido a atributos de anulabilidade).
- O argumento CS8624 - não pode ser usado como uma saída devido a diferenças na anulabilidade 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 anulabilidade do argumento type não corresponde ao tipo de restrição.
- CS8633 - Nullability in constraints for type parameter of method não corresponde às restrições para type parameter of interface method. Em vez disso, considere o uso de 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 anulabilidade do argumento type não corresponde à restrição 'class'.
- CS8643 - A anulabilidade de tipos de referência no especificador de interface explícito não corresponde à interface implementada pelo tipo.
- CS8644 - Type não implementa membro da interface. A anulabilidade dos tipos de referência na interface implementada pelo tipo base não corresponde.
- CS8645 - Membro já está listado na lista de interface em tipo com diferentes anulabilidade de tipos de referência.
- CS8655 - A expressão switch não manipula algumas entradas nulas (não é exaustiva).
- CS8667 - As declarações de método parcial têm nullability inconsistente em restrições para o parâmetro type.
- CS8670 - : Objeto ou inicializador de coleção implicitamente desreferencia possivelmente membro nulo.
- CS8714 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A anulabilidade do argumento type não corresponde à restrição 'notnull'.
- O parâmetro CS8762 - deve ter um valor não nulo ao sair.
- CS8763 - Um método marcado
[DoesNotReturn]
não deve retornar. - CS8764 - A anulabilidade do tipo de retorno não corresponde ao membro substituído (possivelmente devido a atributos de anulabilidade).
- CS8765 - A anulabilidade do tipo de parâmetro não corresponde ao membro substituído (possivelmente devido a atributos de anulabilidade).
- CS8766 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente (possivelmente devido a atributos de anulabilidade).
- CS8767 - Anulabilidade de tipos de referência em tipo de parâmetro de não corresponde implicitamente membro implementado (possivelmente devido a atributos de anulabilidade).
- CS8768 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado (possivelmente devido a atributos de anulabilidade).
- CS8769 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao membro implementado (possivelmente devido a atributos de anulabilidade).
- O método CS8770 - não tem
[DoesNotReturn]
anotação para corresponder ao membro implementado ou substituído. - CS8774 - Membro deve ter um valor não-nulo ao sair.
- CS8776 - Member não pode ser usado neste atributo.
- CS8775 - Membro deve ter um valor não-nulo ao sair.
- CS8777 - Parameter deve ter um valor não nulo ao sair.
- CS8819 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde à declaração parcial do método.
- O parâmetro CS8824 - deve ter um valor não nulo ao sair porque o parâmetro não é nulo.
- CS8825 - Valor de retorno deve 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 'quando' pode corresponder com êxito a esse valor.
O objetivo dos avisos anuláveis é minimizar a chance de que seu aplicativo lance um System.NullReferenceException quando executado. Para atingir esse objetivo, o compilador usa análise estática e emite avisos quando seu código tem construções que podem levar a exceções de referência nulas. Você fornece ao compilador informações para sua análise estática aplicando anotações de tipo e atributos. Essas anotações e atributos descrevem a anulabilidade de argumentos, parâmetros e membros de seus tipos. Neste artigo, você aprenderá diferentes técnicas para lidar com os avisos anuláveis que o compilador gera a partir de sua análise estática. As técnicas descritas aqui são para código C# geral. Aprenda a trabalhar com tipos de referência anuláveis e o núcleo do Entity Framework em Trabalhando com tipos de referência anuláveis.
Você abordará quase todos os avisos usando uma das quatro técnicas:
- Adicionando verificações nulas necessárias.
- Adicionar
?
ou!
anular anotações. - Adicionando atributos que descrevem semântica nula.
- Inicializando variáveis corretamente.
Possível desreferenciação de null
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 implicitamente desreferencia possivelmente membro nulo.
O código a seguir 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 é porque o Container
, c
, pode ter um valor nulo para a States
propriedade. A atribuição de novos estados a uma coleção que pode ser nula causa o aviso.
Para remover esses avisos, você precisa adicionar código para alterar o estado nulo dessa variável para não-nulo antes de desreferenciar ela. O aviso do inicializador da coleção pode ser mais difícil de detetar. O compilador deteta que a coleção talvez-nula quando o inicializador adiciona elementos a ela.
Em muitos casos, você pode corrigir esses avisos verificando se uma variável não é nula antes de desreferenciar ela. Considere o seguinte que adiciona uma verificação nula antes de desreferenciar o message
parâmetro:
void WriteMessageLength(string? message)
{
if (message is not null)
{
Console.WriteLine(message.Length);
}
}
O exemplo a seguir inicializa o armazenamento de backup para o States
e remove o set
acessador. Os consumidores da classe podem modificar o conteúdo da coleção, e o armazenamento para a coleção nunca null
é:
class Container
{
public List<string> States { get; } = new();
}
Outros casos em que você recebe esses avisos podem ser falsos positivos. Você pode ter um método de utilitário privado que testa para null. O compilador não sabe que o método fornece uma verificação nula. Considere o seguinte exemplo que usa um método de utilidade privada, IsNotNull
:
public void WriteMessage(string? message)
{
if (IsNotNull(message))
Console.WriteLine(message.Length);
}
O compilador avisa que você pode estar desreferenciando null quando você escreve 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 ela retorna true
, o estado nulo de message
deve ser não-nulo. Você deve dizer ao compilador esses fatos. Uma maneira é usar o operador de perdão nulo, !
. Você pode alterar a WriteLine
instrução para corresponder ao seguinte código:
Console.WriteLine(message!.Length);
O operador de perdão nulo torna a expressão não-nula , mesmo que fosse talvez-nula sem o !
aplicado. Neste exemplo, uma solução melhor é adicionar um atributo à assinatura de IsNotNull
:
private static bool IsNotNull([NotNullWhen(true)] object? obj) => obj != null;
O System.Diagnostics.CodeAnalysis.NotNullWhenAttribute informa ao compilador que o argumento usado para o obj
parâmetro 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.
Gorjeta
Há um rico conjunto de atributos que você pode usar para descrever como seus métodos e propriedades afetam o estado nulo. Você pode aprender sobre eles no artigo de referência de linguagem sobre Atributos de análise estática anuláveis.
A fixação de um aviso para desreferenciar uma variável talvez-nula envolve uma das três técnicas:
- Adicione uma verificação nula ausente.
- Adicione atributos de análise nulos em APIs para afetar a análise estática de estado nulo do compilador. Esses atributos informam o compilador quando um valor de retorno ou argumento deve ser maybe-null ou not-null depois de chamar o método.
- Aplique o operador
!
null forgiving à expressão para forçar o estado a not-null.
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 é anulável a uma expressão cujo estado nulo é talvez-nulo. Estes avisos são:
- CS8597 - Valor lançado pode ser nulo.
- CS8600 - Convertendo literal nulo ou possível valor nulo para tipo não anulável.
- CS8601 - Possível atribuição de referência nula.
- CS8603 - Possível retorno de referência nulo.
- CS8604 - Possível argumento de referência nula para parâmetro.
- CS8605 - Unboxing um valor possivelmente nulo.
- 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.
O compilador emite esses avisos quando você tenta atribuir uma expressão que é talvez-nula a uma variável que não é anulável. Por exemplo:
string? TryGetMessage(int id) => "";
string msg = TryGetMessage(42); // Possible null assignment.
Os diferentes avisos indicam fornecer detalhes sobre o código, como atribuição, atribuição de unboxing, instruções de retorno, argumentos para métodos e expressões de lançamento.
Você pode executar uma das três ações para lidar com esses avisos. Uma delas é adicionar a ?
anotação para tornar a variável um tipo de referência anulável. Essa alteração pode causar outros avisos. Alterar uma variável de uma referência não anulável para uma referência anulável altera seu 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 que é talvez-nula.
As outras ações instruem o compilador que o lado direito da atribuição não é nulo. A expressão no lado direito pode ser verificada nula antes da atribuição, como mostrado no exemplo a seguir:
string notNullMsg = TryGetMessage(42) ?? "Unknown message id: 42";
Os exemplos anteriores demonstram a atribuição do valor de retorno de um método. Você pode anotar o método (ou propriedade) para indicar quando um método retorna um valor não-nulo. O System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute freqüentemente especifica que um valor de retorno não é nulo quando um argumento de entrada não é nulo. Outra alternativa é adicionar o operador de perdão nulo, !
no lado direito:
string msg = TryGetMessage(42)!;
A fixação de um aviso para atribuir uma expressão talvez-nula a uma variável não-nula envolve uma das quatro técnicas:
- Altere o lado esquerdo da atribuição para um tipo anulável. Essa ação pode introduzir novos avisos quando você desreferenciar essa variável.
- Forneça uma verificação nula antes da atribuição.
- Anote a API que produz o lado direito da atribuição.
- Adicione o operador de perdão 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 é anulável a uma expressão cujo estado nulo é talvez-nulo. Estes avisos são:
- CS8618 - A variável não anulável deve conter um valor não nulo ao sair do construtor. Considere declará-lo como anulável.
- O parâmetro CS8762 - deve ter um valor não nulo ao sair.
Considere a seguinte classe como exemplo:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Nem FirstName
LastName
nem são garantidos inicializados. Se esse código for novo, considere alterar a interface pública. O exemplo acima pode ser atualizado da seguinte forma:
public class Person
{
public Person(string first, string last)
{
FirstName = first;
LastName = last;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
Se você precisar criar um Person
objeto antes de definir o nome, poderá inicializar as propriedades usando um valor padrão não nulo:
public class Person
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
}
Outra alternativa pode ser alterar esses membros para tipos de referência anuláveis. A Person
classe pode ser definida da seguinte forma, se null
deve ser permitida 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 particular 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 System.Diagnostics.CodeAnalysis.MemberNotNullAttribute atributos e System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute . Esses atributos informam ao compilador que um membro não é nulo depois que o método foi chamado. O código a seguir mostra um exemplo de cada um. A Person
classe usa um construtor comum chamado por todos os outros construtores. A Student
classe tem um método auxiliar anotado com o System.Diagnostics.CodeAnalysis.MemberNotNullAttribute atributo:
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";
}
}
Finalmente, você pode usar o operador de perdão nulo para indicar que um membro é inicializado em outro código. Para 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 DbSet
propriedade é inicializada em null!
. Isso informa ao compilador que a propriedade está 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 pega isso. Para obter mais informações sobre como trabalhar com tipos de referência anuláveis e Entity Framework Core, consulte o artigo sobre Trabalhando com tipos de referência anuláveis no EF Core.
A fixação de um aviso para não inicializar um membro não anulável envolve uma das quatro técnicas:
- Altere os construtores ou inicializadores de campo para garantir que todos os membros não anuláveis sejam inicializados.
- Altere um ou mais membros para serem tipos anuláveis.
- Anote quaisquer métodos auxiliares para indicar quais membros estão atribuídos.
- Adicione um inicializador para
null!
indicar que o membro está inicializado em outro código.
Incompatibilidade na declaração de nulidade
Muitos avisos indicam incompatibilidades de anulabilidade entre assinaturas para métodos, delegados ou parâmetros de tipo.
- CS8608 - A anulabilidade dos tipos de referência no tipo não corresponde ao membro substituído.
- CS8609 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro substituído.
- CS8610 - A anulabilidade dos tipos de referência no parâmetro type não corresponde ao membro substituído.
- CS8611 - A anulabilidade dos tipos de referência no parâmetro type não corresponde à declaração parcial do método.
- CS8612 - A anulabilidade dos tipos de referência no tipo não corresponde ao membro implementado implicitamente.
- CS8613 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente.
- CS8614 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao membro implementado implicitamente.
- CS8615 - A anulabilidade dos tipos de referência no tipo não corresponde ao membro implementado.
- CS8616 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado.
- CS8617 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao membro implementado.
- CS8619 - A anulabilidade dos tipos de referência no valor não corresponde ao tipo de destino.
- O argumento CS8620 - não pode ser usado para parâmetro devido a diferenças na anulabilidade dos tipos de referência.
- CS8621 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao delegado de destino (possivelmente devido a atributos de anulabilidade).
- CS8622 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao delegado de destino (possivelmente devido a atributos de anulabilidade).
- O argumento CS8624 - não pode ser usado como uma saída devido a diferenças na anulabilidade 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 anulabilidade do argumento type não corresponde ao tipo de restrição.
- CS8633 - Nullability in constraints for type parameter of method não corresponde às restrições para type parameter of interface method. Em vez disso, considere o uso de 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 anulabilidade do argumento type não corresponde à restrição 'class'.
- CS8643 - A anulabilidade de tipos de referência no especificador de interface explícito não corresponde à interface implementada pelo tipo.
- CS8644 - Type não implementa membro da interface. A anulabilidade dos tipos de referência na interface implementada pelo tipo base não corresponde.
- CS8645 - Membro já está listado na lista de interface em tipo com diferentes anulabilidade de tipos de referência.
- CS8667 - As declarações de método parcial têm nullability inconsistente em restrições para o parâmetro type.
- CS8714 - O tipo não pode ser usado como parâmetro de tipo no tipo genérico ou método. A anulabilidade do argumento type não corresponde à restrição 'notnull'.
- CS8764 - A anulabilidade do tipo de retorno não corresponde ao membro substituído (possivelmente devido a atributos de anulabilidade).
- CS8765 - A anulabilidade do tipo de parâmetro não corresponde ao membro substituído (possivelmente devido a atributos de anulabilidade).
- CS8766 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado implicitamente (possivelmente devido a atributos de anulabilidade).
- CS8767 - Anulabilidade de tipos de referência em tipo de parâmetro de não corresponde implicitamente membro implementado (possivelmente devido a atributos de anulabilidade).
- CS8768 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde ao membro implementado (possivelmente devido a atributos de anulabilidade).
- CS8769 - A anulabilidade dos tipos de referência no tipo de parâmetro não corresponde ao membro implementado (possivelmente devido a atributos de anulabilidade).
- CS8819 - A anulabilidade dos tipos de referência no tipo de retorno não corresponde à declaração parcial do método.
O código a seguir demonstra 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 virtual
método em uma classe base e um override
com nullability 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 os string
e string?
forem invertidos, isso seria permitido porque a classe derivada é mais restritiva. Da mesma forma, as declarações de parâmetros devem corresponder. Os parâmetros no método de substituição podem permitir null 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 a implementação desse método. Ou um tipo de delegado e a expressão para esse delegado podem ser diferentes. Um parâmetro type e o argumento type podem diferir em anulabilidade.
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 Atributos para análise estática anulável para informar o compilador sobre a semântica nula do seu código. O compilador avisa se o código não aderir às promessas desse atributo:
- CS8607 - Um possível valor nulo não pode ser usado para um tipo marcado com
[NotNull]
ou[DisallowNull]
- CS8763 - Um método marcado
[DoesNotReturn]
não deve retornar. - O método CS8770 - não tem
[DoesNotReturn]
anotação para corresponder ao membro implementado ou substituído. - CS8774 - Membro deve ter um valor não-nulo ao sair.
- CS8775 - Membro deve ter um valor não-nulo ao sair.
- CS8776 - Member não pode ser usado neste atributo.
- CS8777 - Parameter deve ter um valor não nulo ao sair.
- O parâmetro CS8824 - deve ter um valor não nulo ao sair porque o parâmetro não é nulo.
- CS8825 - Valor de retorno deve ser não-nulo porque o parâmetro é não-nulo.
Considere o seguinte método:
public bool TryGetMessage(int id, [NotNullWhen(true)] out string? message)
{
message = null;
return true;
}
O compilador produz um aviso porque o message
parâmetro é atribuído null
e o método retorna true
. O NotNullWhen
atributo indica que isso não deveria acontecer.
Para lidar com esses avisos, atualize seu código para que ele corresponda às expectativas dos atributos que você aplicou. Você pode alterar os atributos, ou o algoritmo.
Expressão exaustiva do interruptor
As expressões do switch devem ser exaustivas, o que significa que todos os valores de entrada devem ser manipulados. Mesmo para tipos de referência não anuláveis, o null
valor deve ser contabilizado. O compilador emite avisos quando o valor nulo não é manipulado:
- CS8655 - A expressão switch não manipula algumas entradas nulas (não é exaustiva).
- CS8847 - A expressão switch não manipula algumas entradas nulas (não é exaustiva). No entanto, um padrão com uma cláusula 'quando' 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 ao null
. Para resolver esses erros, você pode adicionar um caso explícito null
ou substituir o { }
pelo _
padrão (descartar). O padrão de descarte corresponde a null, bem como a qualquer outro valor.