Expressar a intenção
Na unidade anterior, você aprendeu como o compilador C# pode executar análise estática para ajudar a proteger contra 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.
Declarando variáveis
Com um contexto anulável habilitado, você tem mais visibilidade de como o compilador vê seu código. Você pode agir de acordo com os avisos gerados a partir de um contexto nulo e, ao fazer isso, está definindo explicitamente suas intenções. Por exemplo, vamos continuar examinando o FooBar
código e examinar a declaração e a atribuição:
// Define as nullable
FooBar? fooBar = null;
Observe o ?
adicionado ao FooBar
. Isso informa ao compilador que você pretende explicitamente que fooBar
seja anulável. Se você não pretende fooBar
ser anulável, mas ainda assim 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 null-forgiving (!
) ao null
, que instrui o compilador que você está inicializando explicitamente essa variável como null. O compilador não emitirá avisos sobre essa referência ser nula.
Uma boa prática é atribuir suas variáveis não anuláveis semnull
valores quando elas forem 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 torno de tipos de referência anuláveis.
Operador de perdão nulo (!
)
Você foi apresentado ao operador de tolerância nula (!
) na seção anterior. Ele diz ao compilador para ignorar o aviso CS8600. Esta é uma maneira de dizer ao compilador que você sabe o que está fazendo, mas vem com a ressalva de que você deve realmente saber o que está fazendo!
Quando você inicializa tipos não anuláveis enquanto um contexto anulável está habilitado, talvez seja necessário pedir perdão explicitamente ao compilador. 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
pode retornar null
. Esta possibilidade null
seria atribuída à fooBar
, que não é anulável neste contexto. No entanto, neste exemplo inventado, sabemos que Find
nunca voltará null
como escrito. Você pode expressar essa intenção para o compilador com o operador null-forpering:
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 Find
método pode ser null
, e tudo bem.
Você também pode aplicar o operador de tolerância nula a um objeto embutido antes de uma chamada de método ou avaliação de propriedade. Considere outro exemplo inventado:
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 o valor retornado porGetFooList
.- O compilador gera um aviso porque
fooList.Find(f => f.Name == "Bar");
o valor atribuído afooList
pode sernull
. - Supondo
fooList
que nãonull
é,Find
pode retornarnull
, mas sabemos que não, então o operador de perdão nulo é aplicado.
Você pode aplicar o operador null-forgiving para fooList
desativar o aviso:
FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;
Nota
Você deve usar o operador de perdão nulo criteriosamente. Usá-lo simplesmente para descartar um aviso significa que você está dizendo ao compilador para não ajudá-lo a descobrir possíveis contratempos nulos. Use-o com moderação e apenas quando tiver certeza.
Para mais informações, consulte ! operador (null-forgiving) (referência C#).
Operador de coalescência nula (??
)
Ao trabalhar com tipos anuláveis, talvez seja necessário avaliar se eles estão atualmente null
e tomar determinadas medidas. Por exemplo, quando um tipo anulável tiver sido atribuído null
ou não tiver sido inicializado, talvez seja necessário atribuir-lhe um valor não nulo. É aí que o operador de coalescência nula (??
) é útil.
Considere o seguinte exemplo:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
salesTax ??= DefaultStateSalesTax.Value;
// Safely use salesTax object.
}
No código C# anterior:
- O
salesTax
parâmetro é definido como sendo um anulávelIStateSalesTax
. - Dentro do corpo do método, o
salesTax
é atribuído condicionalmente usando o operador de coalescência nulo.- Isso garante que se
salesTax
foi passado comonull
que terá um valor.
- Isso garante que se
Gorjeta
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.
}
Aqui está um exemplo de outra linguagem C# comum onde o operador de coalescência nula 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 C# anterior:
- Define uma classe de wrapper genérica, onde o parâmetro de tipo genérico é restrito a
new()
. - O construtor aceita um
T source
parâmetro que é padrão paranull
. - O encapsulado
_source
é inicializado condicionalmente para umnew T()
arquivo .
Para mais informações, confira ?? e ?? = operadores (referência C#).
Operador null-conditional (?.
)
Ao trabalhar com tipos anuláveis, talvez seja necessário executar condicionalmente ações com base no estado de um null
objeto. Por exemplo: na unidade anterior, o FooBar
registro era usado para demonstrar NullReferenceException
por desreferenciação null
. Isso foi causado quando foi ToString
chamado. Considere este mesmo exemplo, mas agora aplicando o operador null-condition:
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 C# anterior:
- Desreferencia
fooBar
condicionalmente, atribuindo o resultado deToString
àstr
variável.- A
str
variável é do tipostring?
(cadeia anulável).
- A
- Ele grava o valor da
str
saída padrão, que não é nada. - A chamada
Console.Write(null)
é válida, por isso não há avisos. - Você receberia um aviso se
Console.Write(str.Length)
ligasse porque estaria potencialmente desreferenciando null.
Gorjeta
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 operador para expressar ainda mais sua intenção. Por exemplo, você pode encadear os ?.
operadores e ??
:
FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown
Para obter mais informações, consulte os operadores ?. e ?[] (condicional nulo).
Resumo
Nesta unidade, você aprendeu sobre como expressar sua intenção de anulabilidade no código. Na próxima unidade, você aplicará o que aprendeu a um projeto existente.