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.
Em um contexto habilitado anulável, o compilador executa a análise estática do código para determinar o estado nulo de todas as variáveis de tipo de referência:
- not-null: A análise estática determina que uma variável tem um valor não nulo.
- Maybe-nulo: A análise estática não consegue determinar que uma variável está atribuída a um valor não nulo.
Estes estados permitem ao compilador fornecer avisos quando pode desreferenciar um valor nulo, lançando um System.NullReferenceException. Estes atributos fornecem ao compilador informação semântica sobre o estado nulo dos argumentos, valores de retorno e membros de objetos. Os atributos esclarecem o estado dos argumentos e retornam valores. O compilador fornece avisos mais precisos quando as suas APIs estão devidamente anotadas com esta informação semântica.
Este artigo fornece uma breve descrição de cada um dos atributos de tipo de referência anuláveis e como usá-los.
Comecemos por um exemplo. Imagine que a sua biblioteca tem a seguinte API que recupera uma cadeia de recursos. Este método foi originalmente compilado em um contexto anulável esquecido :
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
O exemplo anterior segue o padrão familiar Try* no .NET. Há dois parâmetros de referência para essa API: o key e o message. Essa API tem as seguintes regras relacionadas ao estado nulo desses parâmetros:
- Os chamadores não devem passar
nullcomo argumento parakey. - Os chamadores podem passar uma variável cujo valor é
nullcomo argumento paramessage. - Se o
TryGetMessagemétodo retornartrue, o valor demessagenão é nulo. Se o valor de retorno forfalse, o valor demessageé null.
A regra para key pode ser expressa de forma sucinta: key deve ser um tipo de referência não anulável. O message parâmetro é mais complexo. Permite uma variável que é null como o argumento, mas garante, no sucesso, que o out argumento não nullé. Para esses cenários, você precisa de um vocabulário mais rico para descrever as expectativas. O NotNullWhen atributo descreve o estado nulo do argumento usado para o message parâmetro.
Nota
Adicionar esses atributos dá ao compilador mais informações sobre as regras para sua API. Quando o código de chamada é compilado num contexto habilitado para anulação, o compilador avisa os chamadores quando violam essas regras. Esses atributos não permitem mais verificações em sua implementação.
| Atributo | Categoria | Significado |
|---|---|---|
| AllowNull | Pré-condição | Um parâmetro, campo ou propriedade não anulável pode ser nulo. |
| DisallowNull | Pré-condição | Um parâmetro, campo ou propriedade anulável nunca deve ser nulo. |
| TalvezNulo | Pós-condição | Um parâmetro, campo, propriedade ou valor de retorno não anulável pode ser nulo. |
| NotNull | Pós-condição | Um parâmetro anulável, campo, propriedade ou valor de retorno nunca é nulo. |
| MaybeNullWhen | Pós-condição condicional | Um argumento não anulável pode ser nulo quando o método devolve o valor especificado bool . |
| NotNullWhen | Pós-condição condicional | Um argumento anulável não é nulo quando o método devolve o valor especificado bool . |
| NotNullIfNotNull | Pós-condição condicional | Um valor de retorno, propriedade ou argumento não será nulo se o argumento para o parâmetro especificado não for nulo. |
| MembroNotNull | Método e métodos auxiliares de propriedade | O membro listado não é nulo quando o método regressa. |
| MemberNotNullWhen | Método e métodos auxiliares de propriedade | O membro listado não é nulo quando o método devolve o valor especificado bool . |
| DoesNotReturn | Código inacessível | Um método ou propriedade nunca retorna. Por outras palavras, lança sempre uma exceção. |
| DoesNotReturnIf | Código inacessível | Esse método ou propriedade nunca retorna se o parâmetro associado bool tiver o valor especificado. |
As descrições anteriores são uma referência rápida ao que cada atributo faz. As seções a seguir descrevem o comportamento e o significado desses atributos mais detalhadamente.
Pré-condições: AllowNull e DisallowNull
Considere uma propriedade de leitura/gravação que nunca retorna null porque tem um valor padrão razoável. Os chamadores passam null para o acessador definido ao defini-lo para esse valor padrão. Por exemplo, considere um sistema de mensagens que pede um nome de tela em uma sala de chat. Se nenhum for fornecido, o sistema gera um nome aleatório:
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
Quando você compila o código anterior em um contexto nulo esquecido, tudo está bem. Depois de habilitar tipos de referência anuláveis, a ScreenName propriedade se torna uma referência não anulável. Os chamadores não precisam verificar a propriedade retornada para null. Mas agora definir a propriedade para null gera um aviso. Para dar suporte a esse tipo de código, adicione o System.Diagnostics.CodeAnalysis.AllowNullAttribute atributo à propriedade, conforme mostrado no código a seguir:
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
Poderá precisar de adicionar uma using diretiva para System.Diagnostics.CodeAnalysis usar este e outros atributos discutidos neste artigo. O atributo é aplicado à propriedade, não ao set acessador. O AllowNull atributo especifica pré-condições e só se aplica a argumentos. O get acessador tem um valor de retorno, mas sem parâmetros. Portanto, o AllowNull atributo só se aplica ao set acessador.
O exemplo anterior demonstra o que procurar ao adicionar o AllowNull atributo em um argumento:
- O contrato geral para essa variável é que ela não deveria ser
null, então você quer um tipo de referência não anulável. - Há cenários para um chamador passar
nullcomo argumento, embora não sejam o uso mais comum.
Na maioria das vezes, é necessário este atributo para propriedades, ou in, out, e ref argumentos. O AllowNull atributo é a melhor escolha quando uma variável normalmente não é nula, mas você precisa permitir null como uma pré-condição.
Compare isso com cenários para usar DisallowNull: Você usa esse atributo para especificar que um argumento de um tipo de referência anulável não deve ser null. Considere uma propriedade onde null é o valor padrão, mas os clientes só podem defini-la como um valor não nulo. Considere o seguinte código:
public string ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;
O código anterior é a melhor maneira de expressar seu design que poderia ReviewComment ser null, mas não pode ser definido como null. Uma vez que esse código é anulável, você pode expressar esse conceito mais claramente para chamadores usando o System.Diagnostics.CodeAnalysis.DisallowNullAttribute:
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
Em um contexto anulável, o ReviewCommentget acessador poderia retornar o valor padrão de null. O compilador avisa que ele deve ser verificado antes do acesso. Além disso, avisa os chamadores que, mesmo que possa ser null, os chamadores não devem defini-lo explicitamente como null. O DisallowNull atributo também especifica uma pré-condição, não afeta o get acessador. Você usa o DisallowNull atributo quando observa essas características sobre:
- A variável pode estar
nullem cenários centrais, muitas vezes quando instanciada pela primeira vez. - A variável não deve ser explicitamente definida como
null.
Essas situações são comuns em códigos que originalmente eram nulos. Pode ser que as propriedades do objeto sejam definidas em duas operações de inicialização distintas. Pode acontecer que algumas propriedades só sejam definidas depois de algum trabalho assíncrono ser concluído.
Os AllowNull atributos e DisallowNull permitem-lhe especificar que as pré-condições das variáveis podem não corresponder às anotações anuláveis nessas variáveis. Estas anotações fornecem mais detalhes sobre as características da sua API. Essas informações adicionais ajudam os chamadores a usar sua API corretamente. Lembre-se de especificar pré-condições usando os seguintes atributos:
- AllowNull: Um argumento não anulável pode ser nulo.
- DisallowNull: Um argumento nullable nunca deve ser null.
Pós-condições: MaybeNull e NotNull
Suponha que você tenha um método com a seguinte assinatura:
public Customer FindCustomer(string lastName, string firstName)
Provavelmente escreveste um método assim para regressar null quando o nome procurado não era encontrado. O null indica claramente que o registro não foi encontrado. Neste exemplo, você provavelmente alteraria o tipo de retorno de Customer para Customer?. Declarar o valor de retorno como um tipo de referência anulável especifica claramente a intenção dessa API:
public Customer? FindCustomer(string lastName, string firstName)
Por razões cobertas sob a nulidade dos genéricos , essa técnica pode não produzir a análise estática que corresponde à sua API. Podes ter um método genérico que segue um padrão semelhante:
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
O método retorna null quando o item procurado não é encontrado. Você pode esclarecer que o método retorna null quando um item não é encontrado adicionando a MaybeNull anotação ao retorno do método:
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
O código anterior informa os chamadores de que o valor de retorno pode , na verdade, ser nulo. Também informa o compilador que o método pode devolver uma null expressão mesmo que o tipo não seja nulo. Quando você tem um método genérico que retorna uma instância de seu parâmetro type, T, você pode expressar que ele nunca retorna null usando o NotNull atributo.
Você também pode especificar que um valor de retorno ou um argumento não é nulo, mesmo que o tipo seja um tipo de referência anulável. O método a seguir é um método auxiliar que lança se seu primeiro argumento for null:
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
Você pode chamar essa rotina da seguinte forma:
public static void LogMessage(string? message)
{
ThrowWhenNull(message, $"{nameof(message)} must not be null");
Console.WriteLine(message.Length);
}
Depois de habilitar tipos de referência nulos, você deseja garantir que o código anterior seja compilado sem avisos. Quando o método retorna, o value parâmetro é garantido para não ser nulo. No entanto, é aceitável chamar ThrowWhenNull com uma referência nula. Você pode criar value um tipo de referência anulável e adicionar a NotNull pós-condição à declaração de parâmetro:
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
// other logic elided
O código anterior expressa o contrato existente claramente: os chamadores podem passar uma variável com o null valor, mas o argumento é garantido para nunca ser nulo se o método retornar sem lançar uma exceção.
Você especifica pós-condições incondicionais usando os seguintes atributos:
- MaybeNull: Um valor de retorno não anulável pode ser nulo.
- NotNull: Um valor de retorno anulável nunca é nulo.
Pós-condições condicionais: NotNullWhen, MaybeNullWhene NotNullIfNotNull
Você provavelmente está familiarizado com o string método String.IsNullOrEmpty(String). Esse método retorna true quando o argumento é nulo ou uma cadeia de caracteres vazia. É uma forma de null-check: os chamadores não precisam anular o argumento se o método retornar false. Para tornar um método como esse anulável, defina o argumento como um tipo de referência anulável e adicione o NotNullWhen atributo:
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
Isso informa ao compilador que qualquer código onde o valor de retorno está false não precisa de verificações nulas. A adição do atributo informa a análise estática do compilador que IsNullOrEmpty executa a verificação nula necessária: quando ele retorna false, o argumento não nullé .
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.
O String.IsNullOrEmpty(String) método é como mostrado no exemplo anterior. Podes ter métodos semelhantes na tua base de código que verificam o estado dos objetos para valores nulos. O compilador não reconhece métodos personalizados de verificação nula, e tens de adicionar as anotações tu próprio. Quando adicionas o atributo, a análise estática do compilador sabe quando a variável testada está verificada por nulo.
Outro uso para esses atributos é o Try* padrão. As pós-condições ref e out os argumentos são comunicados através do valor de retorno. Considere este método mostrado anteriormente (em um contexto desabilitado anulável):
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
O método anterior segue um idioma típico .NET: o valor de retorno indica se message foi definido para o valor procurado ou, se não for encontrada mensagem, para o valor padrão. Se o método retornar true, o valor de message não é null, caso contrário, o método será definido message como null.
Em um contexto habilitado anulável, você pode comunicar esse idioma usando o NotNullWhen atributo. Ao anotar parâmetros para tipos de referência anuláveis, crie message um string? e adicione um atributo:
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
No exemplo anterior, o valor de message é conhecido por não ser nulo quando TryGetMessage retorna true. Você deve anotar métodos semelhantes em sua base de código da mesma maneira: os argumentos podem ser iguais nulla , e são conhecidos por não serem nulos quando o método retorna true.
Há uma última característica que também podes precisar. Às vezes, o estado nulo de um valor de retorno depende do estado nulo de um ou mais argumentos. Estes métodos devolvem um valor não nulo sempre que certos argumentos não nullsão . Para anotar corretamente esses métodos, use o NotNullIfNotNull atributo . Considere o seguinte método:
string GetTopLevelDomainFromFullUrl(string url)
Se o url argumento não for nulo, a saída não nullserá . Depois de as referências anuláveis estarem ativadas, precisa de adicionar mais anotações se a sua API conseguir aceitar um argumento nulo. Você pode anotar o tipo de retorno conforme mostrado no código a seguir:
string? GetTopLevelDomainFromFullUrl(string? url)
Isso também funciona, mas muitas vezes obriga os chamadores a implementar verificações extra null . O contrato é que o valor de retorno seria null apenas quando o argumento url é null. Para expressar esse contrato, você deve anotar este método conforme mostrado no código a seguir:
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
O exemplo anterior usa o nameof operador para o parâmetro url. O valor de retorno e o argumento foram anotados com a indicação de ? que ambos poderiam ser null. O atributo clarifica ainda que o valor de retorno não é nulo quando o url argumento não nullé .
Você especifica pós-condições condicionais usando estes atributos:
-
MaybeNullWhen: Um argumento não anulável pode ser nulo quando o método devolve o valor especificado
bool. -
NotNullWhen: Um argumento nullable não é nulo quando o método devolve o valor especificado
bool. - NotNullIfNotNull: Um valor de retorno não será nulo se o argumento para o parâmetro especificado não for nulo.
Métodos auxiliares: MemberNotNull e MemberNotNullWhen
Estes atributos especificam a sua intenção quando refatorou código comum de construtores para métodos auxiliares. O compilador C# analisa construtores e inicializadores de campos para garantir que todos os campos de referência não anuláveis sejam inicializados antes de cada construtor retornar. No entanto, o compilador C# não controla as atribuições de campo através de todos os métodos auxiliares. O compilador emite aviso CS8618 quando os campos não são inicializados diretamente no construtor, mas sim em um método auxiliar. Você adiciona o MemberNotNullAttribute a uma declaração de método e especifica os campos que são inicializados para um valor não-nulo no método. Por exemplo, considere o seguinte exemplo:
public class Container
{
private string _uniqueIdentifier; // must be initialized.
private string? _optionalMessage;
public Container()
{
Helper();
}
public Container(string message)
{
Helper();
_optionalMessage = message;
}
[MemberNotNull(nameof(_uniqueIdentifier))]
private void Helper()
{
_uniqueIdentifier = DateTime.Now.Ticks.ToString();
}
}
Você pode especificar vários nomes de campo como argumentos para o construtor de MemberNotNull atributo.
O MemberNotNullWhenAttribute tem um bool argumento. Você usa MemberNotNullWhen em situações em que seu método auxiliar retorna uma bool indicação se seu método auxiliar inicializou campos.
Parar a análise anulável quando o método chamado lança
Alguns métodos, tipicamente auxiliares de exceção, ou outros métodos utilitários, saem sempre lançando uma exceção. Ou, um ajudante lança uma exceção com base no valor de um argumento booleano.
No primeiro caso, você pode adicionar o DoesNotReturnAttribute atributo à declaração de método. A análise de estado nulo do compilador não verifica nenhum código em um método que segue uma chamada para um método anotado com DoesNotReturn. Considere este método:
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}
O compilador não emite nenhum aviso após a chamada para FailFast.
No segundo caso, você adiciona o System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute atributo a um parâmetro booleano do método. Você pode modificar o exemplo anterior da seguinte maneira:
private void FailFastIf([DoesNotReturnIf(true)] bool isNull)
{
if (isNull)
{
throw new InvalidOperationException();
}
}
public void SetFieldState(object? containedField)
{
FailFastIf(containedField == null);
// No warning: containedField can't be null here:
_field = containedField;
}
Quando o valor do argumento corresponde ao DoesNotReturnIf valor do construtor, o compilador não executa nenhuma análise de estado nulo após esse método.
Resumo
Adicionar tipos de referência anuláveis fornece um vocabulário inicial para descrever suas expectativas de APIs para variáveis que podem ser null. Os atributos fornecem um vocabulário mais rico para descrever o estado nulo das variáveis como pré-condições e pós-condições. Esses atributos descrevem mais claramente suas expectativas e fornecem uma experiência melhor para os desenvolvedores que usam suas APIs.
À medida que você atualiza bibliotecas para um contexto anulável, adicione esses atributos para orientar os usuários de suas APIs para o uso correto. Esses atributos ajudam a descrever completamente o estado nulo de argumentos e valores de retorno.
- AllowNull: Um campo, parâmetro ou propriedade não anulável pode ser nulo.
- DisallowNull: Um campo, parâmetro ou propriedade anulável nunca deve ser null.
- MaybeNull: Um campo, parâmetro, propriedade ou valor de retorno não anulável pode ser nulo.
- NotNull: Um campo, parâmetro, propriedade ou valor de retorno anulável nunca é nulo.
-
MaybeNullWhen: Um argumento não anulável pode ser nulo quando o método devolve o valor especificado
bool. -
NotNullWhen: Um argumento nullable não é nulo quando o método devolve o valor especificado
bool. - NotNullIfNotNull: Um parâmetro, propriedade ou valor de retorno não será nulo se o argumento para o parâmetro especificado não for nulo.
- DoesNotReturn: Um método ou propriedade nunca retorna. Por outras palavras, lança sempre uma exceção.
-
DoesNotReturnIf: Este método ou propriedade nunca retorna se o parâmetro associado
booltiver o valor especificado.