C# moderno para quem aprendeu C# há muitos anos
Abrir um projeto C# atual depois de anos sem acompanhar de perto a linguagem é uma experiência estranha. O using System; no topo do arquivo ainda existe, mas boa parte do resto mudou de um jeito que não é imediatamente óbvio olhando de longe.
Não faz sentido transformar isso numa lista cronológica de “o que cada versão trouxe”. O que importa é entender por que o código atual parece mais enxuto e, em alguns pontos, pede um jeito diferente de pensar sobre o mesmo problema de sempre.
Menos código para dizer a mesma coisa
Boa parte da evolução recente de C# é sobre remover cerimônia. Top-level statements tiraram a obrigação de embrulhar um programa simples numa classe Program com um Main estático só para rodar três linhas. Target-typed new deixa o compilador inferir o tipo do lado direito quando ele já está declarado do lado esquerdo:
List<Pedido> pedidos = new();
Nada disso muda o que o programa faz. Muda quanto texto existe entre a intenção e o resultado.
Nulidade virou pergunta, não suposição
Se você aprendeu C# antes de nullable reference types, provavelmente está acostumado a tratar qualquer referência como potencialmente nula e descobrir isso em tempo de execução, geralmente com uma NullReferenceException no pior momento possível.
Com #nullable enable habilitado, string e string? deixam de ser a mesma coisa para o compilador. Um string comum comunica “isso não deveria ser nulo aqui”; um string? comunica o oposto. O compilador não impede erro em tempo de execução, mas avisa em tempo de compilação quando uma referência potencialmente nula é usada sem checagem. É menos mágica e mais disciplina forçada.
Pattern matching deixou de ser só o switch
Pattern matching hoje aparece em if, em expressões de switch e em declarações inteiras, incluindo checagem de propriedades dentro do próprio padrão:
if (pedido is { Status: StatusPedido.Cancelado, Itens.Count: > 0 })
{
// ...
}
Isso substitui uma sequência de if aninhados verificando campo por campo por uma única condição que descreve a forma dos dados esperados.
Records e a ideia de dado imutável por padrão
record trouxe igualdade por valor e uma forma direta de expressar “isto é dado, não é uma entidade com identidade e comportamento mutável”:
public record Endereco(string Rua, string Cidade, string Cep);
Dois Endereco com os mesmos valores são iguais entre si sem sobrescrever Equals manualmente. Para quem vinha de um mundo onde toda classe era mutável por padrão, essa inversão pede um ajuste de hábito, não só de sintaxe.
O que chegou agora, em C# 14
A versão mais recente trouxe extension members, que permitem declarar propriedades e membros estáticos de extensão além dos tradicionais métodos de extensão, e a contextual keyword field, que dá acesso ao campo gerado automaticamente por uma propriedade sem precisar declarar um campo próprio:
public string Nome
{
get => field;
set => field = value?.Trim() ?? string.Empty;
}
Antes disso, qualquer lógica extra num getter ou setter obrigava a declarar um campo privado só para guardar o valor. Agora não.
O que continua igual, e por que isso importa
Nada disso trocou o modelo de execução do CLR, o garbage collector ou a forma como async/await funciona por baixo. C# está mais conciso e mais explícito sobre intenção, mas continua sendo a mesma linguagem orientada a objetos com tipagem estática de sempre. Quem já entende esses fundamentos tem menos para reaprender do que a lista de novidades faz parecer. O trabalho real é reconhecer os padrões novos quando aparecem no meio de um código que, no fundo, ainda resolve os mesmos problemas de sempre.