AAAlison AmorimBlog
alisonamorim.com ↗

Performance

C# 14: A Revolução na Expressividade e Produtividade do Desenvolvedor .NET

O que o C# 14 traz no .NET 10: palavra-chave field, extension members, construtores parciais, atribuição null-conditional e Span, com exemplos.

  • 6 min de leitura
  • Performance
Fonte do texto
Letreiro neon C# 14 sobre grade synthwave, cercado de janelas de código sobre extension methods, construtores parciais e a palavra-chave field
Neste artigo · 10 seções
  1. 1. A Palavra-Chave field: Propriedades Inteligentes Sem Boilerplate
  2. 2. Extension Members: Estendendo Além de Métodos
  3. 3. Construtores e Eventos Parciais: Source Generators Sem Boilerplate
  4. 4. Atribuição Null-Conditional: Menos Verbosidade
  5. 5. Suporte de Primeira Classe para Span<T>
  6. 6. Modificadores em Parâmetros Lambda Simples
  7. 7. nameof com Genéricos Não Vinculados
  8. 8. Operadores de Atribuição Composta Definidos pelo Usuário
  9. Conclusão
  10. Insights & Takeaways

O C# sempre foi uma linguagem que evolui pensando no desenvolvedor. A cada versão, novos recursos surgem não apenas para adicionar funcionalidades, mas para tornar o código mais limpo, mais expressivo e mais prazeroso de escrever. O C# 14, lançado junto com o .NET 10 em novembro de 2025, representa um dos saltos mais significativos nessa jornada.

Este artigo explora as principais novidades do C# 14 de forma didática e prática, mostrando como cada recurso resolve problemas reais do dia a dia e como você pode começar a usá-los imediatamente.

1. A Palavra-Chave field: Propriedades Inteligentes Sem Boilerplate

Uma das frustrações históricas do C# era a necessidade de criar campos privados manualmente sempre que você precisava adicionar lógica customizada a uma propriedade.

O Problema Anterior

Imagine que você quer garantir que uma propriedade string nunca seja null. Antes do C# 14, você era forçado a abandonar a sintaxe elegante de propriedades automáticas:

// Código verboso necessário no C# 13
private string _message;

public string Message
{
    get => _message;
    set => _message = value ?? throw new ArgumentNullException(nameof(value));
}

Essa abordagem tem três problemas:

  1. Verbosidade: 5 linhas para algo que deveria ser simples
  2. Duplicação: O nome do campo (_message) precisa corresponder à propriedade
  3. Manutenção: Se você renomear a propriedade, precisa lembrar de renomear o campo também

A Solução do C# 14

A palavra-chave field permite acessar o campo de apoio gerado automaticamente pelo compilador, eliminando completamente a necessidade de declaração manual:

// C# 14: Elegante e conciso
public string Message
{
    get;
    set => field = value ?? throw new ArgumentNullException(nameof(value));
}

O compilador gera automaticamente um campo privado nos bastidores. Você pode usá-lo diretamente nos acessadores get e set sem nunca declará-lo explicitamente.

Casos de Uso Práticos

Validação de Entrada:

public int Age
{
    get;
    set => field = value >= 0
        ? value
        : throw new ArgumentException("Age cannot be negative");
}

Transformação de Dados:

public string Email
{
    get => field;
    set => field = value?.ToLowerInvariant() ?? string.Empty;
}

Implementação de INotifyPropertyChanged:

public string Name
{
    get => field;
    set
    {
        if (field != value)
        {
            field = value;
            OnPropertyChanged(nameof(Name));
        }
    }
}

Cuidados Importantes

Se sua classe já tem um membro chamado field, você precisa usar @field ou this.field para desambiguar. Mas a recomendação é simples: evite usar field como nome de variável.

Não Sim Sim C# 14 Propriedade Auto-implementada Precisa de Lógica? get; set; C# 13 ou anterior? Criar campo privado_field manualmente Usar palavra-chavefield diretamente

2. Extension Members: Estendendo Além de Métodos

Desde o C# 3.0, métodos de extensão nos permitem adicionar funcionalidade a tipos existentes sem modificá-los. O C# 14 leva isso muito além, permitindo propriedades de extensão, membros estáticos de extensão e até operadores customizados.

A Limitação Histórica

Antes do C# 14, você só podia criar métodos de extensão:

// C# 13: Apenas métodos
public static class StringExtensions
{
    public static bool IsNullOrEmpty(this string str)
        => string.IsNullOrEmpty(str);
}

Se você quisesse adicionar uma propriedade, estava sem sorte. Tinha que criar um método GetXxx(), o que quebrava a naturalidade da sintaxe.

A Nova Sintaxe: Blocos de Extensão

O C# 14 introduz blocos de extensão que agrupam logicamente todos os membros de extensão para um tipo:

public static class EnumerableExtensions
{
    // Bloco de extensão para instâncias
    extension<T>(IEnumerable<T> source)
    {
        // Propriedade de extensão
        public bool IsEmpty => !source.Any();

        // Método de extensão
        public T FirstOrFallback(T fallback)
            => source.FirstOrDefault() ?? fallback;
    }

    // Bloco de extensão para membros estáticos
    extension<T>(IEnumerable<T>)
    {
        // Propriedade estática de extensão
        public static IEnumerable<T> Empty => Enumerable.Empty<T>();

        // Operador customizado
        public static IEnumerable<T> operator +(
            IEnumerable<T> left,
            IEnumerable<T> right)
            => left.Concat(right);
    }
}

Como Usar

O código acima permite uma sintaxe extremamente natural:

var numbers = new List<int> { 1, 2, 3 };

// Usando propriedade de extensão
if (numbers.IsEmpty)
    Console.WriteLine("Lista vazia");

// Usando método de extensão
var first = numbers.FirstOrFallback(0);

// Usando operador de extensão
var combined = numbers + new[] { 4, 5, 6 };

// Usando propriedade estática de extensão
var empty = IEnumerable<int>.Empty;

Vantagens dos Blocos de Extensão

  1. Organização: Todos os membros relacionados em um único bloco lógico
  2. Versatilidade: Propriedades, métodos, operadores - tudo em um lugar
  3. Legibilidade: O código de extensão fica mais próximo de uma definição de classe real
  4. Compatibilidade: Funciona perfeitamente com extensões antigas usando this
IEnumerable<T> Bloco de Extensão IsEmpty Property FirstOrFallback Method + Operator Empty Static Property

3. Construtores e Eventos Parciais: Source Generators Sem Boilerplate

Source generators revolucionaram o desenvolvimento .NET ao permitir geração de código em tempo de compilação. Mas até o C# 14, havia um problema frustrante: construtores e eventos não podiam ser parciais.

O Problema com Construtores

Imagine que você tem um source generator que precisa executar código de inicialização. Antes do C# 14, você era forçado a criar um método parcial e chamá-lo manualmente:

// Config.cs - Código que VOCÊ escreve
public partial class Config
{
    public Config(string path)
    {
        // Você PRECISA lembrar de chamar isso!
        Initialize(path);
    }

    private partial void Initialize(string path);
}

// Config.g.cs - Código GERADO
public partial class Config
{
    private partial void Initialize(string path)
    {
        // Código gerado pelo generator
        LoadConfiguration(path);
    }
}

Isso é puro boilerplate. Você precisa lembrar de chamar o método em todo construtor relevante.

O Problema com Eventos

Para eventos, era ainda pior. Você precisava criar acessadores manuais e chamar métodos gerados:

// Config.cs
public partial class Config
{
    private EventHandler? _configReloaded;

    public event EventHandler ConfigReloaded
    {
        add
        {
            OnConfigReloadedAdded(value);
            _configReloaded += value;
        }
        remove
        {
            OnConfigReloadedRemoved(value);
            _configReloaded -= value;
        }
    }

    private partial void OnConfigReloadedAdded(EventHandler handler);
    private partial void OnConfigReloadedRemoved(EventHandler handler);
}

Muito código para algo que deveria ser automático!

A Solução do C# 14

Agora construtores e eventos podem ser genuinamente parciais:

// Config.cs - Apenas declarações
public partial class Config
{
    public partial Config(string path);
    public partial event EventHandler ConfigReloaded;
}

// Config.g.cs - Implementações geradas
public partial class Config
{
    public partial Config(string path)
    {
        // Código gerado automaticamente
        LoadConfiguration(path);
    }

    private EventHandler? _configReloaded;
    public partial event EventHandler ConfigReloaded
    {
        add { _configReloaded += value; }
        remove { _configReloaded -= value; }
    }
}

Regras Importantes

Para construtores parciais:

  • Deve haver exatamente uma declaração e uma implementação
  • Apenas a implementação pode ter inicializadores (this() ou base())
  • Apenas uma declaração parcial pode usar sintaxe de construtor primário
  • Não pode ser static

Para eventos parciais:

  • Deve haver exatamente uma declaração e uma implementação
  • A declaração é field-like (sem acessadores)
  • A implementação deve ter acessadores add e remove explícitos
partial Config(string path); Compila projeto Gera implementação partial Config(string path) { ... } Zero boilerplate manual! Desenvolvedor Declaração (você) Generator Implementação (gerada)

4. Atribuição Null-Conditional: Menos Verbosidade

Um padrão extremamente comum em C# é verificar se algo é null antes de atribuir um valor a uma propriedade. O C# 14 finalmente torna isso elegante.

Antes do C# 14

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

Três linhas para algo conceitualmente simples: "se não for null, atribua".

Com C# 14

customer?.Order = GetCurrentOrder();

Uma linha. Claro. Direto.

Como Funciona

  • Se customer for null, a atribuição não acontece e nenhuma exceção é lançada
  • Importante: GetCurrentOrder() não é chamado se customer for null
  • Isso evita side effects desnecessários

Funciona com Atribuição Composta

// Incremento seguro
cart?.Total += newItemPrice;

// Concatenação segura
logger?.Messages += "\nNew log entry";

// Mas NÃO funciona com ++ e --
count?.Value++;  // ❌ Erro de compilação

Quando Usar

✅ Bom para:

  • Atribuições a propriedades que podem ser null
  • Atualização de estado opcional
  • Código defensivo em bibliotecas públicas

❌ Não use quando:

  • A atribuição ser pulada causa comportamento incorreto
  • Você precisa de feedback explícito sobre o estado null

5. Suporte de Primeira Classe para Span<T>

O C# 14 transforma Span<T> e ReadOnlySpan<T> de tipos especializados para cidadãos de primeira classe da linguagem.

O Problema Anterior

public double CalculateAverage<T>(ReadOnlySpan<T> values)
    where T : INumber<T>
{
    // Lógica de cálculo
}

// C# 13: Você PRECISA especificar o tipo
var avg = CalculateAverage<int>(new[] { 1, 2, 3 });

Com C# 14

// C# 14: Inferência automática!
var avg = CalculateAverage(new[] { 1, 2, 3 });

Conversões Implícitas Adicionadas

O C# 14 reconhece as relações entre T[], Span<T> e ReadOnlySpan<T> e adiciona conversões implícitas que tornam o código muito mais natural:

void ProcessData(ReadOnlySpan<int> data) { /* ... */ }

// Tudo isso agora funciona implicitamente:
int[] array = { 1, 2, 3 };
ProcessData(array);  // ✅ Array → ReadOnlySpan

Span<int> span = stackalloc int[3];
ProcessData(span);  // ✅ Span → ReadOnlySpan

ReadOnlySpan<int> readOnlySpan = array;  // ✅ Array → ReadOnlySpan

Spans como Extension Receivers

Agora você pode criar extension methods com Span<T> como receiver, e eles funcionam perfeitamente com arrays:

public static class SpanExtensions
{
    public static int Sum(this ReadOnlySpan<int> span)
    {
        int total = 0;
        foreach (var item in span)
            total += item;
        return total;
    }
}

// Funciona diretamente com array!
int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum();  // ✅ Usa a extensão de Span

Cuidado com Overload Resolution

A nova inferência pode causar ambiguidades. Se você tem tanto uma sobrecarga IEnumerable<T> quanto ReadOnlySpan<T>, o compilador pode escolher a de span onde antes escolhia a de enumerável:

// Pode vincular ao método de span agora
void Process(IEnumerable<int> items) { /* ... */ }
void Process(ReadOnlySpan<int> items) { /* ... */ }

int[] data = { 1, 2, 3 };
Process(data);  // Qual é chamado? C# 14 pode escolher span!

Solução: Use casting explícito ou OverloadResolutionPriorityAttribute para controlar a escolha.

6. Modificadores em Parâmetros Lambda Simples

Lambdas agora suportam ref, in, out, scoped e ref readonly sem precisar especificar o tipo do parâmetro.

Antes

delegate void ApplyDiscount(ref decimal price);

// C# 13: Tipo obrigatório com modificador
ApplyDiscount apply = (ref decimal price) => { price *= 0.9m; };

Agora

// C# 14: Modificador sem tipo!
ApplyDiscount apply = (ref price) => { price *= 0.9m; };

Mais conciso e consistente com a sintaxe normal de lambda.

7. nameof com Genéricos Não Vinculados

Pequena mas útil: agora você pode usar nameof com genéricos sem especificar o tipo:

// C# 13: Precisa de tipo arbitrário
Console.WriteLine(nameof(List<string>));  // "List"

// C# 14: Sintaxe mais limpa
Console.WriteLine(nameof(List<>));  // "List"

Perfeito para logging e diagnósticos onde o tipo específico não importa.

8. Operadores de Atribuição Composta Definidos pelo Usuário

Você agora pode sobrecarregar +=, -= e outros operadores compostos diretamente, permitindo otimizações que não eram possíveis antes:

public struct Money
{
    public decimal Amount { get; set; }
    public string Currency { get; set; }

    // Operador binário (cria nova instância)
    public static Money operator +(Money a, Money b)
    {
        if (a.Currency != b.Currency)
            throw new InvalidOperationException("Moedas diferentes");
        return new Money { Amount = a.Amount + b.Amount, Currency = a.Currency };
    }

    // C# 14: Operador composto (modifica in-place)
    public void operator +=(Money b)
    {
        if (this.Currency != b.Currency)
            throw new InvalidOperationException("Moedas diferentes");
        this.Amount += b.Amount;  // Sem alocação!
    }
}

A versão += evita criar um objeto Money temporário, economizando alocações em loops críticos de performance.

Conclusão

O C# 14 não é apenas sobre adicionar recursos - é sobre remover fricção. Cada uma dessas melhorias ataca um ponto de dor específico que desenvolvedores enfrentavam diariamente:

  • field elimina boilerplate de propriedades
  • Extension members expandem o que você pode estender
  • Construtores/eventos parciais liberam o poder dos source generators
  • Null-conditional assignment torna código defensivo mais limpo
  • Span improvements fazem código performático parecer natural
  • Lambda modifiers facilitam código de alta performance
  • nameof improvements simplificam logging

O C# continua sua jornada de se tornar uma linguagem onde código idiomático é código rápido, e onde código limpo é código produtivo.

Insights & Takeaways

Para Desenvolvedores:

  • Atualize suas propriedades: Revise propriedades com campos privados e considere migrar para field. Seu código ficará mais limpo.
  • Explore extension members: Se você mantém uma biblioteca de utilities, blocos de extensão podem organizar melhor seu código.
  • Aproveite source generators: Com construtores e eventos parciais, geradores ficaram muito mais poderosos. É hora de revisitar cenários onde você evitou usá-los.
  • Simplifique null checks: Procure por padrões if (x != null) x.Property = ... e substitua por x?.Property = ....

Para Arquitetos:

  • Revise bibliotecas públicas: Extension members podem melhorar a API de bibliotecas existentes sem breaking changes.
  • Planeje migração gradual: C# 14 é compatível com código C# 13. Você pode adotar recursos incrementalmente.
  • Atenção a overload resolution: As mudanças em Span<T> podem afetar qual método é chamado em cenários ambíguos.

Conceitos-Chave:

  1. field é contextual - só funciona dentro de acessadores de propriedade.
  2. Extension blocks agrupam membros logicamente, mas não mudam como são usados.
  3. Membros parciais requerem exatamente uma declaração e uma implementação.
  4. Null-conditional não executa o lado direito se o lado esquerdo for null.
  5. Span conversions são implícitas, mas podem causar surpresas em overload resolution.

Próximos Passos:

  • Atualize seus projetos para .NET 10 e comece a experimentar.
  • Use ferramentas como Rider ou VS 2026 que oferecem sugestões para usar novos recursos.
  • Compartilhe conhecimento: ensine sua equipe sobre esses recursos através de exemplos práticos.
  • Monitore breaking changes: embora raras, algumas mudanças podem afetar código existente.

O C# 14 prova que a linguagem continua evoluindo na direção certa: menos cerimônia, mais clareza, melhor performance. Aproveite!

Comente no LinkedIn

A conversa sobre este artigo acontece no LinkedIn, onde ele também foi publicado. Discordou de algo, viu de outro jeito em produção? Conta lá.

Leia também