Operadores nulos do C#

Dica

Este artigo faz parte da seção Conceitos Básicos para desenvolvedores que conhecem pelo menos uma linguagem de programação e estão aprendendo C#. Se você é novo em programação, comece com os tutoriais Comece agora. Para ter acesso à referência completa do operador, consulte operadores de acesso de membro e operadores de coalescência nula na referência de linguagem.

O C# fornece vários operadores que tornam o código seguro nulo conciso. Em vez de aninhar proteções if (x != null) em todo o código, esses operadores permitem que você expresse acesso nulo seguro, valores de fallback e testes nulos em uma única expressão.

Este artigo aborda ?. e ?[] para acesso condicional nulo, ?? para a coalescagem nula, ??= para atribuição de uniões nulas e is null/is not null para correspondência de padrões nulos.

Acesso de membro condicional nulo ?.

O ?. operador acessa um membro somente quando o objeto não é nulo. Quando o objeto é null, a expressão inteira é avaliada como null em vez de gerar 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

Os curto-circuitos do operador ?.: quando o lado esquerdo for null, tudo o que estiver à direita será pulado. Nenhuma chamada de método é executada e nenhum efeito colateral ocorre.

Você pode encadear vários ?. operadores em uma única expressão. A cadeia para no primeiro null encontrado:

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 de indexador condicional nulo ?[]

O ?[] operador aplica o mesmo comportamento de curto-circuito ao indexador e ao acesso à matriz. Use quando a coleção em si 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 de condicional nulo de cadeia

Encadeie vários ?. 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 inteira retorna null.

Invocação de delegado thread-safe

?. fornece uma maneira limpa e thread-safe de invocar um delegado ou gerar um evento. A expressão delegada é avaliada somente uma vez, portanto, não há janela para outro thread cancelar a assinatura 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!

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

Coalescência nula??

O operador ?? retorna seu operando à esquerda quando não é nulo, e seu operando à 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, portanto a ?? b ?? c é avaliado como a ?? (b ?? c). O primeiro valor não nulo vence. Um padrão comum é encadear ?. com ??: use ?. para atravessar com segurança um encadeamento possivelmente nulo e, em seguida, ?? para substituir por um padrão se o encadeamento retornar null. Para obter um exemplo completo, consulte Combinar operadores nulos.

Atribuição de coalescagem nula ??=

O ??= operador atribui o valor à direita a uma variável somente quando a variável é null. Use-o para inicialização lenta:

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 somente quando a variável é null. Quando a variável já tem um valor, o lado direito não é avaliado.

Atribuição condicional nula (C# 14)

A partir do C# 14, você pode usar ?. e ?[] como destinos de atribuição. A atribuição é executada somente quando o objeto esquerdo 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 somente quando o lado esquerdo é conhecido como não nulo.

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

Os padrões is null 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.");
}

Prefira is null mais == null para verificações nulas. O operador pode ser sobrecarregado, o que significa que pode retornar mesmo quando não é , se um tipo definir um operador de igualdade personalizado. O is null padrão sempre testa 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, geralmente você combina vários desses operadores. Uma expressão pode percorrer com segurança um grafo de objeto profundo, aplicar um fallback e, em seguida, 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 tolerante a nulos !

O operador ! de sufixo suprime avisos anuláveis. Acrescente ! para dizer ao compilador que "essa expressão definitivamente não é nula". O operador não tem efeito no runtime. Ele afeta apenas a análise de 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

Use ! com moderação e somente quando tiver informações 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 ! compromete a finalidade de tipos de referência anuláveis. Para obter uma explicação completa, consulte tipos de referência anuláveis.

Consulte também