Atribuição condicional nula no C# 14

A atribuição condicional nula é um recurso do C# 14 que permite usar os operadores ?. e ?[] no lado esquerdo de uma atribuição ou de uma atribuição composta. Com ela, você atribui um valor a uma propriedade, campo ou indexador apenas quando o objeto não é nulo, sem escrever um if para isso.

Esse recurso simplifica o código ao evitar verificações explícitas de nulidade. Além disso, ele reduz o risco de exceções do tipo NullReferenceException em tempo de execução.

O que é a atribuição condicional nula

Desde o C# 6, os operadores ?. e ?[] permitem ler membros de um objeto que pode ser nulo. Se o objeto for null, a expressão inteira resulta em null, em vez de lançar uma exceção.

No entanto, até o C# 13 esses operadores só funcionavam para leitura. Ou seja, você podia escrever var name = customer?.Name, mas não customer?.Name = "Ana".

A atribuição condicional nula remove essa restrição. Segundo a documentação de novidades do C# 14, os operadores de acesso condicional agora podem aparecer no lado esquerdo de uma atribuição ou de uma atribuição composta.

O C# 14 chegou junto com o .NET 10, em novembro de 2025. Portanto, para usar o recurso, você precisa do SDK do .NET 10 ou de um projeto configurado com LangVersion 14 ou superior.

Como os operadores ?. e ?[] funcionam

Antes de avançar, vale relembrar o comportamento básico dos operadores. O ?. acessa um membro apenas quando o objeto à esquerda não é nulo.

Por sua vez, o ?[] faz o mesmo com o acesso por índice, como em arrays, listas e dicionários. Se a coleção for null, o runtime nem calcula o índice.

Esses operadores também fazem curto-circuito. Em outras palavras, quando um elo da cadeia resulta em null, o runtime ignora todo o restante da expressão, sem chamadas de método e sem efeitos colaterais.

Outro detalhe importante é que o runtime avalia o operando da esquerda no máximo uma vez. Assim, ele não pode virar null entre a verificação e o uso.

O problema antes da atribuição condicional nula

Antes do C# 14, não era possível usar ?. como parte de uma atribuição. Portanto, para atribuir um valor a uma propriedade de um objeto que poderia ser nulo, você precisava de uma verificação explícita:

if (customer is not null)
{
    customer.Order = GetCurrentOrder();
}

Esse padrão funciona, mas introduz código repetitivo. Além disso, ele reduz a clareza quando aparece em vários pontos do sistema ou em estruturas encadeadas.

Imagine, por exemplo, um objeto com dois ou três níveis de navegação. Nesse caso, cada nível exige mais uma condição no if, e a intenção real do código fica escondida.

if (order is not null && order.Customer is not null)
{
    order.Customer.LastOrderDate = DateTime.UtcNow;
}

Repare que a linha que importa é a atribuição. Todo o resto é proteção contra nulos, ou seja, ruído para quem lê o código.

Como usar a atribuição condicional nula no C# 14

A partir do C# 14, você pode reescrever o primeiro exemplo assim:

customer?.Order = GetCurrentOrder();

Essa expressão faz a atribuição apenas se customer for diferente de null. Caso contrário, o programa nem chama o método GetCurrentOrder() e nenhum erro acontece.

Da mesma forma, o exemplo encadeado fica bem mais enxuto com a atribuição condicional nula:

order?.Customer?.LastOrderDate = DateTime.UtcNow;

Com isso, a intenção do código aparece logo na primeira leitura. Afinal, a linha diz exatamente o que faz: atualizar a data, se houver pedido e cliente.

Quando a atribuição condicional nula avalia o lado direito

A documentação descreve essa operação como null-safe. Isso significa que o runtime só avalia o lado direito do = se a cadeia de acesso no lado esquerdo não resultar em null.

A documentação de operadores de acesso a membros mostra esse comportamento com um indexador:

values?[2] = GenerateNextIndex();

int GenerateNextIndex() => index++;

Nesse exemplo, o método GenerateNextIndex só roda quando o array values não é nulo. Em outras palavras, o código acima equivale a este bloco:

if (values is not null)
{
    values[2] = GenerateNextIndex();
}

A mesma regra vale para cadeias mais longas. Em a?.b?.c = M(), o método M só roda se a e a.b existirem.

Esse ponto é fundamental para entender a atribuição condicional nula. Afinal, se o lado direito tiver efeitos colaterais, eles também deixam de acontecer quando o alvo é nulo.

Propriedades, campos e indexadores

A atribuição condicional nula funciona com propriedades, campos e indexadores. Por exemplo, o compilador do C# 14 aceita todas as linhas abaixo:

// Propriedade
person?.FirstName = "Scott";

// Indexador de array ou lista
messages?[5] = "five";

// Indexador de dicionário
settings?["theme"] = "dark";

Nos três casos, a regra é a mesma. Se o objeto da esquerda for null, nada acontece, e o valor da direita nem chega a ser calculado.

Regras de comportamento da atribuição condicional nula

Para usar o recurso com segurança, vale conhecer as regras que a especificação da atribuição condicional nula define. Elas são poucas, mas evitam surpresas.

  • O operador ?. não altera o valor da propriedade nem avalia a expressão à direita se o objeto for null.
  • A atribuição é suportada para propriedades, campos e indexadores.
  • O operador ?[] também pode ser usado para atribuir em coleções indexadas.
  • Todas as formas de atribuição composta são permitidas.
  • O valor atual da propriedade não influencia a avaliação, apenas o objeto que a contém.

Esse último item merece atenção. Por exemplo, em a?.b = M(), o método M roda sempre que a não é nulo, mesmo que a.b já seja null.

O resultado da expressão

Na maioria dos casos, você usa a atribuição condicional nula como uma instrução isolada. Ainda assim, a expressão tem um valor, assim como qualquer atribuição em C#.

Se você usar esse valor, o tipo precisa ser conhecido como tipo de valor ou tipo de referência. Por isso, em código genérico com um T sem restrição, a especificação mostra que o compilador recusa o uso do resultado:

class Container<T>
{
    public T? Field;
}

void Update<T>(Container<T>? container, T value)
{
    (container?.Field = value).ToString(); // erro: 'T' não pode se tornar anulável
    container?.Field = value;              // ok
}

Na prática, isso raramente aparece. Mesmo assim, saber dessa regra ajuda a entender mensagens de erro em cenários genéricos.

Atribuição condicional nula com operadores compostos

Além da atribuição simples, a linguagem também passou a aceitar operadores compostos junto com ?.. Assim, você pode usar +=, -=, *=, /= e os demais operadores desse tipo.

Por exemplo:

order?.Total += item.Price;

Nesse caso, a operação += só acontece se order não for nulo. Caso contrário, nada acontece, e o programa nem lê item.Price.

Esse formato é bastante útil em acumuladores e contadores opcionais. Por exemplo, você pode somar métricas em um objeto de estatísticas que só existe quando o recurso está habilitado:

// stats só existe quando a coleta de métricas está ativa
stats?.TotalBytes += payload.Length;
stats?.Errors += failedCount;

Com isso, você não precisa de um if para cada linha. Ao mesmo tempo, o código continua seguro quando stats é nulo.

Limitações com incremento e decremento

Os operadores de incremento e decremento (++ e --) não são suportados com ?. no lado esquerdo. Tentar usá-los resulta em erro de compilação.

Exemplo de código inválido:

counter?.Value++; // não permitido

A equipe da linguagem discutiu esse ponto e optou por não suportar esses operadores nesta versão. Portanto, quando precisar incrementar, use a forma composta equivalente:

counter?.Value += 1; // permitido

O resultado é o mesmo e o código continua curto. Por isso, essa limitação quase não pesa no dia a dia.

Outras restrições do recurso

A expressão condicional continua não sendo uma variável. Por isso, você não pode passar a?.b como argumento ref ou out, nem fazer uma atribuição por referência com ela.

Process(ref order?.Total); // erro

Além disso, a desconstrução não aceita acessos condicionais no lado esquerdo. Nesse caso, a solução é escrever atribuições separadas:

(order?.Total, customer?.Name) = (100m, "Ana"); // erro

order?.Total = 100m;     // ok
customer?.Name = "Ana";  // ok

Segundo a própria especificação, esse cenário deve ser raro. Assim, dividir em duas linhas não representa um custo relevante.

Diferença entre ?.= e ??=

É comum confundir a atribuição condicional nula com o operador ??=, que existe desde o C# 8. No entanto, os dois resolvem problemas opostos.

O ??= atribui um valor quando a variável é nula. Já a atribuição condicional nula atribui um valor quando o objeto que contém o membro não é nulo.

// ??= : preenche a propriedade se ela estiver vazia
customer.Address ??= new Address();

// ?. = : atribui somente se customer existir
customer?.Address = new Address();

Os dois também podem trabalhar juntos. Por exemplo, customer?.Address ??= new Address() cria o endereço apenas se o cliente existir e se ele ainda não tiver um.

Vale notar que o ??= trabalha sobre o valor do membro, enquanto a atribuição condicional nula trabalha sobre o objeto que contém o membro. Por isso, cada operador responde a uma pergunta diferente.

Para entender melhor o papel dos tipos anuláveis nessas operações, veja o artigo sobre nullable types no .NET.

Aplicações práticas da atribuição condicional nula

A atribuição condicional nula é especialmente útil quando a nulidade acontece de forma natural e a ausência da operação não deve gerar erro. Em outras palavras, quando "não fazer nada" é o comportamento correto para um objeto nulo.

Casos comuns incluem:

  • Manipulação de modelos em APIs REST, onde propriedades podem ou não estar presentes.
  • Acesso a objetos parcialmente carregados ou com carregamento preguiçoso (lazy loading).
  • Cenários em que o lado direito tem efeitos colaterais e deve rodar apenas se a cadeia de acesso for válida.

Atribuição condicional nula em uma API

Considere um endpoint que recebe uma atualização parcial de um pedido. Nesse caso, alguns objetos aninhados podem chegar nulos no corpo da requisição.

public void ApplyPatch(OrderPatch patch, Order order)
{
    // Atualiza o endereço de entrega apenas se ele existir no pedido
    order.Shipping?.Street = patch.Street;
    order.Shipping?.Number = patch.Number;

    // Registra a alteração apenas se o histórico estiver carregado
    order.History?.Add(new HistoryEntry("Shipping updated"));
}

Repare que a última linha usa o ?. tradicional, para chamar um método. Já as duas primeiras usam a atribuição condicional nula do C# 14, para gravar valores.

Exemplo com dados carregados sob demanda

Outro cenário comum envolve navegações que o ORM nem sempre carrega. Por exemplo, ele pode trazer um pedido sem o cliente, dependendo da consulta.

// Customer pode não ter sido incluído na consulta
order.Customer?.LastOrderDate = order.CreatedAt;

Dessa forma, o código não quebra quando a consulta não trouxe a navegação. No entanto, avalie se esse silêncio é o comportamento desejado, porque às vezes a ausência do dado indica um bug na consulta.

Atribuição condicional nula em testes e refatorações

Ao refatorar um código antigo, procure blocos if que protegem uma única atribuição. Esses blocos costumam ser bons candidatos para a atribuição condicional nula.

No entanto, faça a troca com testes cobrindo os dois caminhos: objeto nulo e objeto preenchido. Assim, você garante que o comportamento continua o mesmo depois da mudança.

Além disso, verifique se o projeto inteiro já compila com o C# 14. Caso contrário, o compilador vai acusar erro de sintaxe nas linhas novas.

Considerações de desempenho e cuidados

Como o lado direito só roda quando o caminho da esquerda não resulta em null, a atribuição condicional nula evita chamadas desnecessárias de métodos. Isso pode trazer ganhos em estruturas encadeadas profundas ou em lógica condicional distribuída.

Na prática, o compilador gera um código equivalente ao if manual. Portanto, não espere um ganho mágico, mas também não há custo extra em relação à verificação explícita.

Entretanto, tome cuidado quando o lado direito contém lógica crítica ou efeitos colaterais. Afinal, essa lógica não vai rodar se o lado esquerdo for null, e isso pode passar despercebido em uma revisão de código.

// Cuidado: SaveAuditLog só roda se report não for nulo
report?.AuditId = SaveAuditLog(user);

Nesse exemplo, o log de auditoria some silenciosamente quando report é nulo. Por isso, se a chamada precisa acontecer sempre, mova-a para uma linha própria, antes da atribuição.

Outro cuidado envolve a legibilidade. Encadear muitos ?. em uma única linha pode esconder regras de negócio importantes, então prefira cadeias curtas e nomes claros.

Quando preferir o if tradicional

Nem todo if (x is not null) deve virar uma atribuição condicional nula. Se o bloco faz várias operações sobre o mesmo objeto, o if continua mais claro.

Da mesma forma, se o objeto nulo representa um erro, ignorar a atribuição esconde o problema. Nesse caso, lançar uma exceção ou retornar um resultado de falha costuma ser a melhor escolha.

Em resumo, use o recurso quando o objeto nulo for um estado esperado e válido. Para os demais casos, mantenha a verificação explícita e trate a ausência do dado de forma consciente.

Conclusão

A atribuição condicional nula introduzida no C# 14 representa uma evolução na forma como tratamos a nulidade na linguagem. Com ela, os operadores ?. e ?[] deixam de servir apenas para leitura e passam a valer também para escrita.

Com isso, você reduz verificações explícitas, melhora a legibilidade e escreve código menos propenso a falhas por referência nula. Além disso, a integração com operadores compostos amplia bastante os cenários de uso.

Por fim, lembre que o lado direito só roda quando o alvo existe. Se quiser conhecer outras novidades da mesma versão, leia sobre o novo modelo de extension members do C# 14.

FAQ

Em qual versão do C# a atribuição condicional nula foi lançada?

O recurso chegou no C# 14, lançado junto com o .NET 10. Para usá-lo, você precisa do SDK do .NET 10 ou de LangVersion 14 ou superior.

O lado direito da atribuição sempre é executado?

Não, o lado direito só roda quando o objeto à esquerda não é nulo. Se ele for null, métodos e expressões da direita são ignorados.

Posso usar ++ e -- com ?. no lado esquerdo?

Não, incremento e decremento geram erro de compilação. Use += 1 ou -= 1 no lugar.

A atribuição condicional nula funciona com indexadores?

Sim, o operador ?[] permite atribuir em arrays, listas e dicionários. A atribuição só acontece se a coleção não for nula.

Qual a diferença entre ?.= e ??=?

O ??= atribui quando o valor atual é nulo. Já ?. com = atribui quando o objeto que contém o membro não é nulo.