C# 12 na Prática: Como Escrever Código mais Limpo com Primary Constructors e Collection Expressions

domingo, 4 de outubro de 2026 | ReadingTime
C# 12 na Prática: Como Escrever Código mais Limpo com Primary Constructors e Collection Expressions

O ecossistema .NET continua avançando com um objetivo muito claro: reduzir o código boilerplate sem abrir mão da segurança de tipos e do alto desempenho. Com o lançamento do .NET 8 e do C# 12, a Microsoft entregou melhorias focadas na produtividade do desenvolvedor, transformando padrões repetitivos em construções nativas elegantes.


Para quem constrói APIs web, microserviços e sistemas corporativos no dia a dia, duas funcionalidades se destacam: os Primary Constructors em classes e structs convencionais e as Collection Expressions com o spread operator (..). Se você já cansou de declarar campos privados readonly apenas para injeção de dependência ou de usar diferentes sintaxes para inicializar arrays e listas, essas novidades resolvem diretamente essas dores.


Neste artigo prático, vamos analisar como refatorar código legado escrito em .NET 6/7 para as convenções do C# 12 e .NET 8, alinhando boa arquitetura (Clean Code e DDD) com eficiência de memória.


1. Primary Constructors (Construtores Primários)


O Problema do Código Legado


Nas versões .NET 6 e .NET 7, a injeção de dependências em serviços de aplicação exigia a declaração de campos privados readonly e de um construtor explícito.


C#

// ❌ Padrão Tradicional (.NET 6 / .NET 7) 

namespace PaymentContext.Domain.Services; 

 

public class PaymentProcessor : IPaymentProcessor 

{ 

   private readonly IPaymentRepository _paymentRepository; 

   private readonly INotificationService _notificationService; 

   private readonly ILogger<PaymentProcessor> _logger; 

 

   public PaymentProcessor( 

       IPaymentRepository paymentRepository, 

       INotificationService notificationService, 

       ILogger<PaymentProcessor> logger) 

   { 

       _paymentRepository = paymentRepository ?? throw new ArgumentNullException(nameof(paymentRepository)); 

       _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService)); 

       _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 

   } 

 

   public async Task ProcessAsync(Payment payment, CancellationToken cancellationToken) 

   { 

       _logger.LogInformation("Processando pagamento {PaymentId}", payment.Id); 

       await _paymentRepository.SaveAsync(payment, cancellationToken); 

       await _notificationService.NotifySuccessAsync(payment.CustomerEmail); 

   } 

} 

A Solução no C# 12 (.NET 8)


Com os Primary Constructors, os parâmetros são declarados diretamente no cabeçalho da classe e ficam automaticamente visíveis em todo o seu escopo.


C#

// ✅ Padrão C# 12 (.NET 8) 

namespace PaymentContext.Domain.Services; 


public class PaymentProcessor( 

   IPaymentRepository paymentRepository, 

   INotificationService notificationService, 

   ILogger<PaymentProcessor> logger) : IPaymentProcessor 

{ 

   public async Task ProcessAsync(Payment payment, CancellationToken cancellationToken) 

   { 

       // Os parâmetros do construtor primário estão disponíveis no corpo da classe 

       logger.LogInformation("Processando pagamento {PaymentId}", payment.Id); 

        

       await paymentRepository.SaveAsync(payment, cancellationToken); 

       await notificationService.NotifySuccessAsync(payment.CustomerEmail); 

   } 

} 


2. Collection Expressions e o Spread Operator (..)


Sintaxe Fragmentada no .NET 6/7


Inicializar e concatenar diferentes tipos de coleções exigia métodos distintos para cada estrutura de dados (new[], List<T>, Span<T>).


C#

// ❌ Inicialização e concatenação legada (.NET 6 / .NET 7) 

int[] baseCodes = new int[] { 101, 102, 103 }; 

int[] extraCodes = new int[] { 201, 202 }; 

 

List<int> allCodesList = new List<int>(baseCodes); 

allCodesList.AddRange(extraCodes); 

allCodesList.Add(301); 

 

int[] result = allCodesList.ToArray(); 


Unificação no C# 12 com Colchetes [...]


Com as Collection Expressions, a criação de coleções é padronizada através do uso de colchetes [...] e do spread operator .. para achatar elementos.


C# 

// ✅ Padrão C# 12 (.NET 8) 

int[] baseCodes = [101, 102, 103]; 

int[] extraCodes = [201, 202]; 

 

// Concatenação limpa e declarativa usando o spread operator (..) 

List<int> allCodesList = [.. baseCodes, .. extraCodes, 301]; 

 

// Alocação direta em memória de pilha/assembly via ReadOnlySpan sem pressão no Garbage Collector 

ReadOnlySpan<byte> activeStatus = [1, 2, 4]; 


Casos de Uso Reais e Performance


Onde Usar


  • Primary Constructors: Ideais para injeção de dependência em Use Cases, Services, Handlers do MediatR e API Controllers.
  • Collection Expressions: Ideais para inicializar buffers, retornar coleções imutáveis, passar argumentos para métodos e manipular ReadOnlySpan<T> com alocação zero no Heap.


Onde EVITAR


  1. Parâmetros Mutáveis em Primary Constructors: Lembre-se de que parâmetros de construtores primários em classes normais não são campos readonly automáticos (diferente de records). Se o valor for reatribuído dentro de métodos da classe, isso pode gerar bugs colaterais. Se a imutabilidade estrita for um requisito no seu modelo de domínio (DDD), continue atribuindo o valor a um campo readonly explícito ou utilize record.
  2. Inicializações com Lógica Complexa: Se o seu construtor precisa rodar validações extensas ou executar rotinas pesadas, prefira um construtor tradicional para manter a clareza.


Ganho de Performance no .NET 8


  • Zero-Allocation Data Spans: Ao inicializar um ReadOnlySpan<T> com Collection Expressions, o compilador do C# 12 embute os bytes diretamente no segmento de dados da aplicação (Assembly), evitando qualquer alocação na Heap.
  • Dimensionamento de Capacidade: Ao usar expressões de coleção com a sintaxe List<T> list = [.. a, .. b], o compilador pré-calcula a contagem total de elementos antes de alocar a lista, prevenindo múltiplos redimensionamentos internos (Array.Resize).


Conclusão e Call to Action


O C# 12 e o .NET 8 trazem um avanço direto na produtividade técnica e no desempenho de execução. A adotação de Primary Constructors e Collection Expressions elimina o código repetitivo mantendo o código conciso, legível e seguro.


Comece a refatorar seus serviços hoje mesmo para tirar proveito dessas inovações!


👉 Quer impulsionar sua carreira em desenvolvimento .NET? Explore as melhores vagas de TI e continue evoluindo seu conhecimento técnico com a comunidade da MasterStack!

Marcio Mazeu
Escrito por
Marcio Mazeu

.NET Software Developer

Analista-Programador .NET graduado em Engenharia de Computação, especializado na modernização de aplicações e arquitetura de APIs (C#, SQL Server, JavaScript). Apaixonado pelo desenvolvimento de ferramentas de alta performance e pelo aprendizado contínuo, contribui para a criação de soluções tecnológicas confiáveis e focadas na experiência do usuário.