Expressando intenção
Na unidade anterior, você aprendeu como o compilador C# pode executar a análise estática para ajudar a protegê-lo de NullReferenceException
. Você também aprendeu como habilitar um contexto anulável. Nesta unidade, você aprenderá mais sobre como expressar explicitamente sua intenção dentro de um contexto anulável.
Como declarar variáveis
Com um contexto anulável habilitado, você tem mais visibilidade sobre como o compilador vê seu código. Você pode executar ações com base nos avisos gerados de um contexto habilitado para valor nulo e, fazendo isso, você está definindo explicitamente suas intenções. Por exemplo, vamos continuar examinando o código FooBar
e analisar a declaração e a atribuição:
// Define as nullable
FooBar? fooBar = null;
Observe o ?
adicionado a FooBar
. Isso informa ao compilador que você pretende explicitamente que fooBar
seja anulável. Se você não pretende que fooBar
permita um valor nulo, mas ainda deseja evitar o aviso, considere o seguinte:
// Define as non-nullable, but tell compiler to ignore warning
// Same as FooBar fooBar = default!;
FooBar fooBar = null!;
Este exemplo adiciona o operador tolerante a nulos (!
) a null
, que instrui o compilador de que você está inicializando explicitamente essa variável como nula. O compilador não emitirá avisos sobre essa referência ser nula.
Uma boa prática é atribuir suas variáveis não anuláveis valores que não sejam null
quando elas são declaradas, se possível:
// Define as non-nullable, assign using 'new' keyword
FooBar fooBar = new(Id: 1, Name: "Foo");
Operadores
Conforme discutido na unidade anterior, o C# define vários operadores para expressar sua intenção em relação aos tipos de referência anuláveis.
Operador tolerante a nulos (!
)
Você foi apresentado ao operador tolerante a nulos (!
) na seção anterior. Ele informa ao compilador para ignorar o aviso CS8600. Essa é uma maneira de informar ao compilador que você sabe o que está fazendo, mas vem com a advertência de que você deve realmente saber o que está fazendo!
Quando você inicializar tipos não anuláveis enquanto um contexto anulável estiver habilitado, talvez seja necessário pedir explicitamente ao compilador para perdoar. Por exemplo, considere o seguinte código:
#nullable enable
using System.Collections.Generic;
var fooList = new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
FooBar fooBar = fooList.Find(f => f.Name == "Bar");
// The FooBar type definition for example.
record FooBar(int Id, string Name);
No exemplo anterior, FooBar fooBar = fooList.Find(f => f.Name == "Bar");
gera um aviso CS8600 porque Find
poderá retornar null
. null
Isso pode ser atribuído a fooBar
, que não permite valor anulável neste contexto. No entanto, neste exemplo artificial, sabemos que Find
nunca será retornado null
como escrito. Você pode expressar essa intenção para o compilador com o operador tolerante a nulos:
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;
Observe o !
no final de fooList.Find(f => f.Name == "Bar")
. Isso informa ao compilador que você sabe que o objeto retornado pelo método Find
pode ser null
e não tem problema.
Você também pode aplicar o operador tolerante a valores nulos a um objeto embutido antes de uma chamada de método ou de uma avaliação de propriedade. Considere outro exemplo artificial:
List<FooBar>? fooList = FooListFactory.GetFooList();
// Declare variable and assign it as null.
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!; // generates warning
static class FooListFactory
{
public static List<FooBar>? GetFooList() =>
new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
No exemplo anterior:
GetFooList
é um método estático que retorna um tipo anulável,List<FooBar>?
.fooList
é atribuído ao valor retornado porGetFooList
.- O compilador gera um aviso sobre
fooList.Find(f => f.Name == "Bar");
porque o valor atribuído afooList
pode sernull
. - Supondo que
fooList
não sejanull
,Find
pode retornarnull
, mas sabemos que não será, portanto, o operador tolerante a nulos será aplicado.
Você pode aplicar o operador tolerante a nulos a fooList
para desabilitar o aviso:
FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;
Observação
Você deve usar o operador que tolerante a valores nulos com cautela. Usá-lo simplesmente para descartar um aviso significa que você está dizendo ao compilador para não ajudá-lo a descobrir possíveis incidentes nulos. Use-o com moderação e apenas quando tiver certeza disso.
Para obter mais informações, veja Operador ! (tolerante a valores nulos) (referência de C#).
Operador de avaliação de nulo (??
)
Ao trabalhar com tipos anuláveis, talvez seja necessário avaliar se eles estão null
no momento e tomar certas ações. Por exemplo, quando um tipo anulável tiver sido atribuído null
ou não tiver sido inicializado, talvez seja necessário atribuí-los um valor não nulo. É aí que o operador de avaliação de nulo (??
) é útil.
Considere o exemplo a seguir:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
salesTax ??= DefaultStateSalesTax.Value;
// Safely use salesTax object.
}
No código anterior do C#:
- O parâmetro
salesTax
é definido como sendoIStateSalesTax
anulável. - No corpo do método, o
salesTax
é condicionalmente atribuído usando o operador de avaliação de nulo.- Isso garante que, se
salesTax
tiver sido passado comonull
, ele terá um valor.
- Isso garante que, se
Dica
Isso é funcionalmente equivalente ao seguinte código C#:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
if (salesTax is null)
{
salesTax = DefaultStateSalesTax.Value;
}
// Safely use salesTax object.
}
Veja um exemplo de outra expressão C# comum em que o operador de avaliação de nulo pode ser útil:
public sealed class Wrapper<T> where T : new()
{
private T _source;
// If given a source, wrap it. Otherwise, wrap a new source:
public Wrapper(T source = null) => _source = source ?? new T();
}
O código anterior do C#:
- Define uma classe wrapper genérica, em que o parâmetro de tipo genérico é restrito a
new()
. - O construtor aceita um parâmetro
T source
que usa como padrãonull
. - O
_source
encapsulado é condicionalmente inicializado para umnew T()
.
Para obter mais informações, confira Operadores ?? e ??= (referência de C#).
Operador condicional nulo (?.
)
Ao trabalhar com tipos anuláveis, talvez seja necessário executar ações condicionalmente com base no estado de um objeto null
. Por exemplo: na unidade anterior, o registro FooBar
foi usado para demonstrar NullReferenceException
pela desreferência de null
. Isso foi causado quando ToString
foi chamado. Considere este mesmo exemplo, mas agora aplicando o operador condicional nulo:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
var str = fooBar?.ToString();
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
O código anterior do C#:
- Desreferenciar
fooBar
condicionalmente, atribuindo o resultado deToString
à variávelstr
.- A variável
str
é do tipostring?
(cadeia de caracteres anulável).
- A variável
- Ele grava o valor de
str
na saída padrão, que é nada. - A chamada a
Console.Write(null)
é válida, ou seja, não há nenhum aviso. - Você receberia um aviso se fosse chamar
Console.Write(str.Length)
porque estaria potencialmente desreferenciando o nulo.
Dica
Isso é funcionalmente equivalente ao seguinte código C#:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
string str = (fooBar is not null) ? fooBar.ToString() : default;
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
Você pode combinar o operador para expressar ainda mais sua intenção. Por exemplo, você poderia encadear os operadores ?.
e ??
:
FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown
Para obter mais informações, veja Operadores ?. e ?[] (condicional nulo).
Resumo
Nesta unidade, você aprendeu a expressar a sua intenção de nulidade no código. Na próxima unidade, você aplicará o que aprendeu a um projeto existente.