Construtores Primários em Classes e Structs: Uma Revolução no C#
Os construtores primários, inicialmente introduzidos para records no C# 9 e expandidos no C# 12 para classes e structs, ganharam ainda mais refinamento no C# 13 (lançado com o .NET 9 em 2024). Essa funcionalidade permite que você defina parâmetros de inicialização diretamente na declaração de uma classe ou struct, eliminando a necessidade de escrever construtores tradicionais e campos explícitos em muitos casos. Vamos mergulhar no que isso significa, com exemplos práticos, comparações antes e depois, e uma análise dos benefícios técnicos.
Antes: O Mundo Verboso dos Construtores Tradicionais
No C# tradicional, criar uma classe com propriedades imutáveis ou inicialização obrigatória exigia um código repetitivo. Veja este exemplo de uma classe Produto:
public class Produto
{
private readonly int _id;
private readonly string _nome;
private readonly decimal _preco;
public Produto(int id, string nome, decimal preco)
{
_id = id;
_nome = nome;
_preco = preco;
}
public int Id => _id;
public string Nome => _nome;
public decimal Preco => _preco;
}
Aqui, temos:
- Campos privados para garantir imutabilidade.
- Um construtor explícito para inicializar os campos.
- Propriedades só de leitura para expor os valores.
São 13 linhas para algo simples. Agora, imagine isso em dezenas de classes – a boilerplate se acumula rapidamente.
Depois: Construtores Primários no C# 13
Com construtores primários, o mesmo código pode ser reduzido drasticamente:
public class Produto(int id, string nome, decimal preco)
{
public int Id => id;
public string Nome => nome;
public decimal Preco => preco;
}
Ou, se você preferir propriedades automáticas:
public class Produto(int id, string nome, decimal preco)
{
public int Id { get; } = id;
public string Nome { get; } = nome;
public decimal Preco { get; } = preco;
}
O que mudou?
- Os parâmetros do construtor primário (id, nome, preco) são automaticamente capturados como campos implícitos.
- Você pode usá-los diretamente em propriedades ou métodos sem declará-los como campos privados.
- O compilador gera o código subjacente para você.
Exemplo com Structs
Os construtores primários também funcionam em structs. Antes:
public struct Ponto
{
private readonly double _x;
private readonly double _y;
public Ponto(double x, double y)
{
_x = x;
_y = y;
}
public double X => _x;
public double Y => _y;
}
Depois, com C# 13:
public struct Ponto(double x, double y)
{
public double X => x;
public double Y => y;
}
A redução de verbosidade é ainda mais valiosa em structs, onde a alocação de memória e a eficiência são críticas.
Benefícios Técnicos
- Redução de Boilerplate: Menos código significa menos chance de erros e maior legibilidade.
- Imutabilidade Simplificada: Os parâmetros do construtor primário são imutáveis por padrão, alinhando-se às boas práticas de design orientado a objetos.
- Manutenção Facilitada: Adicionar ou remover um parâmetro não exige ajustar campos e construtores separadamente.
- Integração com Records: A sintaxe unificada entre classes, structs e records torna o código mais consistente.
- Performance: Não há impacto mensurável no runtime; o compilador otimiza tudo em tempo de compilação.
Comparação: Antes x Depois
Aqui está uma tabela comparando os dois cenários:
Aspecto | Antes (Construtor Tradicional) | Depois (Construtor Primário) |
---|---|---|
Linhas de Código | 13 linhas para Produto | 6-8 linhas para Produto |
Declaração de Campos | Explicita (private readonly) | Implícita (gerada pelo compilador) |
Flexibilidade | Menor; exige ajustes manuais | Maior; parâmetros são reutilizáveis |
Legibilidade | Baixa em classes grandes | Alta, especialmente em classes simples |
Imutabilidade | Garantida manualmente | Garantida por padrão |
Casos de Uso | Qualquer cenário | Melhor para classes/structs simples |
Exemplo Avançado: Lógica Adicional
Construtores primários não eliminam a possibilidade de lógica personalizada. Veja:
public class Pedido(int id, decimal valorTotal, DateTime data)
{
public int Id => id;
public decimal ValorTotal => valorTotal;
public DateTime Data => data;
public bool EstaVencido => DateTime.Now > data.AddDays(30);
// Lógica no corpo do construtor
public Pedido
{
if (valorTotal < 0) throw new ArgumentException("Valor total não pode ser negativo.");
}
}
Aqui, o corpo do construtor primário permite validações, enquanto os parâmetros são usados diretamente nas propriedades.
Limitações
- Complexidade: Para classes com múltiplos construtores ou lógica pesada, os construtores tradicionais ainda podem ser necessários.
- Legibilidade em Casos Complexos: Muitos parâmetros podem tornar a declaração confusa (ex.: class Exemplo(int a, string b, double c, bool d)).
O Futuro dos Construtores Primários
O C# 13 refina os construtores primários, mas a funcionalidade ainda está evoluindo. A equipe do .NET tem sugerido em blogs e discussões no GitHub que futuras versões podem:
- Adicionar suporte a inicialização condicional diretamente nos parâmetros.
- Integrar melhor com ferramentas de serialização (como JSON).
- Permitir anotações nos parâmetros implícitos (ex.: [JsonPropertyName]).
Com o foco contínuo da linguagem em reduzir boilerplate e aumentar a expressividade, os construtores primários devem se tornar um pilar do desenvolvimento em C#, especialmente em aplicações onde a simplicidade e a imutabilidade são prioridades. Fique de olho nas próximas .NET Conf para mais novidades!