C# 12 - Primary Constructors


   Hoje vou apresentar um novo recurso previsto para o C# 12 chamado de primary constructors ou construtores primários.

Esta previsto para o C# 12 um novo recurso chamado de Construtores primários que visa simplificar a inicialização de propriedades em classes, principalmente para classes com muitas propriedades.

Antes de apresentar os Construtores Primários, vamos ver como os construtores funcionam atualmente no C#.

No C# um construtor é um método especial chamado quando um objeto de classe é criado sendo usados para inicializar o estado de um objeto, o que normalmente envolve definir os valores das propriedades da classe.

A seguir temos um exemplo clássico de uma classe Funcionario com um construtor :

public class Funcionario
{
  
public string Nome { get; set; }
  
public string Sobrenome { get; set; }
  
public DateTime DataContratacao { get; set; }
  
public decimal Salario { get; set; }

  
public Funcionario(string nome, string sobrenome, DateTime dataContratacao, decimal salario)
   {
     Nome = nome;
     Sobrenome = sobrenome;
     DataContratacao = dataContratacao;
     Salario = salario;
   }
}

O que são construtores primários?

Um construtor primário é um recurso que permite definir e inicializar propriedades diretamente na lista de parâmetros do construtor. Esse recurso elimina a necessidade de código repetido e torna seu código mais conciso e legível.

E você pode se surpreender, mas já temos o recurso dos construtores primários desde o C# 9 definidos no recurso record.  Considere o seguinte record:

public record Pessoa(string Nome, int Idade)
{
  
public Pessoa() : this("Maria", 31) { }
}

var maria = new Person("Maria", 31);
 

Podemos ver que os records definem seu construtor primário diretamente na definição.

Usando construtores primários

Vamos começar com um exemplo de uso de construtores primários com uma classe Funcionario. Vejamos a seguir como definir uma classe Funcionario usando um construtor primário.

Para isso vamos usar o VS 2022 Preview e criar um projeto Console App usando a versão do  .NET 8.0 preview :

E para poder usar a versão do C# 12 no projeto teremos que incluir no arquivo de projeto a tag:
<LangVersion>preview</LangVersion> :

<Project Sdk="Microsoft.NET.Sdk">

   <
PropertyGroup>
    <
OutputType>Exe</OutputType>
    <
TargetFramework>net8.0</TargetFramework>
    <
ImplicitUsings>enable</ImplicitUsings>
    <
LangVersion>preview</LangVersion>
    <
Nullable>enable</Nullable>
    </
PropertyGroup>
</
Project>

No código abaixo temos o uso do construtor primário
 
var funcionario1 = new Funcionario("Maria", "Silveira", new DateTime(2022, 1, 1), 50000);

Console.WriteLine(funcionario1.Nome);

Console.ReadKey();

public class Funcionario(string nome, string sobrenome, DateTime dataContratacao, decimal salario)
{
  
public string Nome { get; init; } = nome;
  
public string Sobrenome { get; init; } = sobrenome;
  
public DateTime DataContratacao { get; init; } = dataContratacao;
  
public decimal Salario { get; init; } = salario;
}

Neste exemplo, definimos uma classe Funcionario com quatro propriedades: Nome, Sobrenome, DataContratacao e Salario. Também usamos a palavra-chave init para tornar as propriedades somente leitura e as inicializamos diretamente na lista de parâmetros do construtor primário.

A seguir usamos o código abaixo para criar um novo objeto Funcionario usando o Construtor Primário :

var funcionario1 = new Funcionario("Maria", "Silveira", new DateTime(2022, 1, 1), 50000);

Executando o projeto iremos obter:

O objeto Funcionario é criado com os parâmetros nome, sobrenome, dataContratacao e salario, que são usados para inicializar as propriedades correspondentes.

Observe que a classe Funcionario não tem um método construtor com um construtor sem parâmetros, portanto, você obterá um erro do compilador se tentar criar um objeto usando o construtor padrão.

Os construtores primários funcionam bem com classes que possuem muitas propriedades, pois reduzem a quantidade de código clichê necessário para inicializá-los. Além disso, o uso de construtores primários torna seu código mais legível e reduz o risco de erros causados por instruções de inicialização ausentes ou ordenadas incorretamente.

Assim,  a capacidade de uma classe ou struct em C# ter mais de um construtor fornece generalidade, mas às custas de algum tédio na sintaxe da declaração, porque a entrada do construtor e o estado da classe precisam ser claramente separados.

Os construtores primários colocam os parâmetros de um construtor no escopo para que toda a classe seja usada para inicialização ou diretamente como estado do objeto. A desvantagem é que qualquer outro construtor deve chamar por meio do construtor primário.

Há também uma diferença entre o recurso usado nos records e o novo construtor primário.

Vamos comparar a definição de um record com uma classe que usa um construtor primário :

1- record

public record Pessoa(string Nome, int Idade)
{
  
public Pessoa() : this("Maria", 31) { }
}

2- Classe

public class Pessoa(string nome, int idade)
{
  
public string Nome { get; set; } = nome;
  
public int Idade { get; set; } = idade;
}

A diferença é que na classe os parâmetros são escritos em letras minúsculas e no record usamos PascalCase ou seja maiúsculas e minúsculas.

A razão aqui é simples : em contraste com os records, o construtor primário atua mais como um argumento do que como uma propriedade. É por isso que você pode ver que definimos esses parâmetros para as propriedades.

Assim como os records, quando você declara um construtor primário, você DEVE usá-lo ao declarar construtores explícitos da seguinte forma:

var produto1 = new Produto();

var produto2 = new Produto("Produto1");

var produto3 = new Produto("Meu produto", 1);

Console.Readkey();

public class Produto(string nome, int categoriaId)
{
  
public Produto() : this("Default", -1) { }
  
public Produto(string nome) : this(nome, 0) { }

  
public string? Nome => nome;
  
public int CategoriaId => categoriaId;
  
public string? ProdutoId => $"{nome}-{categoriaId}";

}

Como você pode ver, é requerido o uso da palavra-chave this() nos demais construtores ,  que invocam o construtor primário. Se você omitir o this, o compilador vai gerar a seguinte mensagem de erro:

Erro CS8862 Um construtor declarado em um tipo com lista de parâmetros deve ter o inicializador de construtor 'this'.

O comportamento do Construtor Primário em structs é o mesmo, visto que já tínhamos uma sintaxe semelhante na record struct com o construtor sem parâmetros trazido pelo C# 11.

E estamos conversados.

"Sabemos que permanecemos nele, e ele em nós, porque ele nos deu do seu Espírito.
E vimos e testemunhamos que o Pai enviou seu Filho para ser o Salvador do mundo."
1 João 4:13,14

Referências:


José Carlos Macoratti