Operadores nulos em C#

Sugestão

Este artigo faz parte da secção de Fundamentos para programadores que conhecem pelo menos uma linguagem de programação e estão a aprender C#. Se és novo na programação, começa primeiro pelos tutoriais para começar . Para a referência completa dos operadores, consulte Operadores de acesso a membro e Operadores de coalescência nula na documentação da linguagem.

O C# fornece vários operadores que tornam o código null-safe conciso. Em vez de incorporar if (x != null) guardas por todo o seu código, estes operadores permitem-lhe expressar acesso com segurança nula, valores de substituição e testes nulos numa única expressão.

Este artigo cobre ?. e ?[] para acesso nulo-condicional, ?? para nul-coalescência, ??= para atribuição nula-coalescente e is null/is not null para correspondência de padrões nulos.

Acesso a membros nulos e condicional ?.

O ?. operador acede a um membro apenas quando o objeto não é nulo. Quando o objeto é null, toda a expressão é avaliada como null em vez de lançar um NullReferenceException:

string? name = null;

// Without ?., accessing a member on null throws NullReferenceException:
// int len = name.Length; // throws if name is null

// ?. returns null instead of throwing:
int? len = name?.Length;
Console.WriteLine(len.HasValue); // False

name = "C#";
Console.WriteLine(name?.Length); // 2

O ?. operador faz curto-circuito: quando o lado esquerdo é null, tudo o que está à direita é ignorado. Nenhuma chamada de método é executada e não ocorrem efeitos secundários.

Podes encadear múltiplos ?. operadores numa única expressão. A corrente pára no primeiro null que encontra:

string? input = null;

// Chain ?. across multiple method calls — short-circuits at the first null:
string? upper = input?.Trim()?.ToUpperInvariant();
Console.WriteLine(upper ?? "(none)"); // (none)

input = "  hello  ";
Console.WriteLine(input?.Trim()?.ToUpperInvariant()); // HELLO

Acesso ao indexador nulo-condicional ?[]

O ?[] operador aplica o mesmo comportamento de curto-circuito ao indexador e ao acesso ao array. Utilize-o quando a própria coleção puder ser null:

string[]? tags = null;

// ?[] accesses an element only when the collection is non-null
string? first = tags?[0];
Console.WriteLine(first ?? "(none)"); // (none)

tags = ["csharp", "dotnet", "nullable"];
Console.WriteLine(tags?[0]);          // csharp

Operadores nulos-condicionais em cadeia

Encadeie múltiplos ?. operadores para percorrer um caminho de referências potencialmente nulas. A cadeia entra em curto-circuito no primeiro null:

var order = new Order("ORD-001", null);

// Each ?. short-circuits when null: Customer is null, so Address and City are never accessed
string? city = order.Customer?.Address?.City;
Console.WriteLine(city ?? "(no city)"); // (no city)

var fullOrder = new Order("ORD-002",
    new Customer("Alice", new Address("123 Main St", "Springfield", "IL")));

Console.WriteLine(fullOrder.Customer?.Address?.City); // Springfield

Quando Customer é null, nem Address nem City é avaliado. A expressão total retorna null.

Invocação de delegado seguro para threads

?. fornece uma forma limpa e segura de threads para invocar um delegado ou levantar um evento. A expressão do delegado é avaliada apenas uma vez, pelo que não há janela para outro thread cancelar a subscrição entre a verificação nula e a invocação:

EventHandler? clicked = null;

// No subscribers — ?.Invoke does nothing instead of throwing NullReferenceException
clicked?.Invoke(null, EventArgs.Empty);

clicked += (_, _) => Console.WriteLine("Button clicked!");

// With a subscriber — ?.Invoke calls the handler
clicked?.Invoke(null, EventArgs.Empty);
// Output: Button clicked!

Este padrão substitui o antigo if (clicked != null) clicked(...) idioma.

Coalescência nula ??

O ?? operador devolve o seu operando da mão esquerda quando este não é nulo, e o seu operando da mão direita quando o da esquerda é null. Use-o para fornecer um valor padrão:

string? username = null;

// ?? returns the right-hand value when the left-hand is null
string display = username ?? "Guest";
Console.WriteLine(display); // Guest

username = "alice";
display  = username ?? "Guest";
Console.WriteLine(display); // alice

?? é associativo à direita, por isso a ?? b ?? c é avaliado como a ?? (b ?? c). O primeiro valor não nulo vence. Um padrão comum é encadear ?. com ??: use ?. para atravessar em segurança uma cadeia nula-possível, depois use ?? para substituir por um valor padrão se a cadeia devolveu null. Para um exemplo completo, veja Combinar operadores nulos.

Atribuição de coalescência nula ??=

O ??= operador atribui o valor direito a uma variável apenas quando a variável é null. Use-o para inicialização preguiçosa (lazy initialization):

List<string>? cache = null;

// ??= assigns only when the variable is null
cache ??= LoadData();
Console.WriteLine(cache.Count); // 3

// cache is already non-null, so LoadData() isn't called again
cache ??= LoadData();
Console.WriteLine(cache.Count); // 3

static List<string> LoadData() => ["alpha", "beta", "gamma"];

A expressão à direita é avaliada apenas quando a variável possui o valor null. Quando a variável já tem um valor, o lado direito não é avaliado de todo.

Atribuição nulo-condicional (C# 14)

A partir de C# 14, podes usar ?. e ?[] como alvos de atribuição. A atribuição só é executada quando o objeto da esquerda não é nulo:

AppConfig? config = new AppConfig();

// Assigns only when config is non-null (C# 14)
config?.Theme = "dark";
Console.WriteLine(config?.Theme); // dark

AppConfig? missing = null;
missing?.Theme = "light";                         // no-op: missing is null
Console.WriteLine(missing?.Theme ?? "(no config)"); // (no config)

O lado direito é avaliado apenas quando se sabe que o lado esquerdo não é nulo.

Correspondência de padrões nulos: is null e is not null

Os is null padrões e is not null testam se uma expressão é null:

string? input = null;

// is null is the preferred test — unaffected by operator overloading
if (input is null)
{
    Console.WriteLine("No input provided.");
}

// == null also works, but a custom == operator can change its behavior
if (input == null)
{
    Console.WriteLine("Still no input.");
}

Prefiro is null em vez == null para verificações de nulo. O == operador pode ser sobrecarregado, o que significa que x == null pode retornar true mesmo que x não seja null, se o tipo definir um operador de igualdade personalizado. O is null padrão testa sempre a referência nula real, independentemente da sobrecarga do operador.

string? value = "hello";

if (value is not null)
{
    Console.WriteLine(value.ToUpper()); // HELLO
}

Combinar operadores nulos

Na prática, muitas vezes combina-se vários destes operadores. Uma expressão pode percorrer com segurança um grafo de objetos profundos, aplicar um fallback e depois proteger o resultado:

Order? order = GetPendingOrder();

// Chain ?. for safe traversal, ?? for a fallback, is null for a clear guard
string city = order?.Customer?.Address?.City ?? "unknown";

if (order is null)
{
    Console.WriteLine("No pending order.");
}
else
{
    Console.WriteLine($"Shipping to: {city}");
}
// Output: No pending order.

Operador de perdão nulo !

O ! operador postfix suprime avisos anuláveis. Anexe ! para dizer ao compilador "esta expressão definitivamente não é nula." O operador não tem efeito em tempo de execução. Só afeta a análise do estado nulo do compilador.

string? name = FindUser("alice");

// Use ! only when you have information the compiler doesn't.
// FindUser guarantees a non-null result for known usernames.
int length = name!.Length;
Console.WriteLine(length); // 5

Usa ! com moderação, e só quando tiveres informação que o compilador não tem. Exemplos incluem testes que passam null intencionalmente para validar a lógica de verificação de argumentos, ou chamar um método cujo contrato garante um retorno não nulo para uma entrada conhecida. O uso ! excessivo anula o propósito dos tipos de referência anuláveis. Para uma explicação completa, veja Tipos de referência anuláveis.

Ver também