Menu

Construtor em C#: padrão, com parâmetros, encadeamento com this() e base()

Como os construtores funcionam em C#: o construtor padrão implícito e quando ele desaparece, construtores com parâmetros e sobrecarregados, encadeamento com this(...) e base(...), a ordem em que tudo executa, construtores static e private, e os construtores primários do C# 12.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um construtor é o código que executa quando você cria um objeto com new. Ele tem o mesmo nome da classe, não tem tipo de retorno, e o trabalho dele é colocar o novo objeto em um estado válido antes que alguém o use.

Saída:

Mug: 8.50
Rejected: price

Como o construtor recusa um preço negativo, não pode existir um Product com preço negativo. Essa garantia é o principal motivo para escrever construtores em vez de definir campos de fora.

O construtor padrão, e quando ele desaparece

Se uma classe não declara nenhum construtor, o compilador fornece um público e sem parâmetros que não faz nada além de executar os inicializadores dos campos. É por isso que new BankAccount() funciona em uma classe que só tem campos.

No momento em que você declara qualquer construtor, esse implícito deixa de ser gerado:

class Product
{
    public string Name;
    public Product(string name) { Name = name; }
}

var p = new Product();   // error CS7036: There is no argument given that corresponds to the required parameter 'name'

Isso surpreende quando um serializador, um ORM ou uma restrição genérica new() precisa de um construtor sem parâmetros. Se você quer os dois, declare você mesmo o sem parâmetros:

public Product() { }                       // or
public Product() : this("Unnamed") { }     // delegate to the other one (next section)

Um construtor pode ser public, internal, protected ou private. O acesso dele decide quem pode criar objetos com ele.

Sobrecarga e encadeamento com this(...)

Uma classe pode ter vários construtores com listas de parâmetros diferentes. Para não copiar as mesmas atribuições em cada um, encadeie-os: : this(...) chama outro construtor da mesma classe antes de o corpo atual executar.

Saída:

new Pizza():
  full constructor: medium, classic, 0
  size-only constructor body
  parameterless constructor body
Result: medium classic with 0 toppings

A cadeia executa primeiro o construtor mais completo e volta até aquele que você chamou. Mantenha o trabalho real (validação, atribuições) em um construtor e deixe os outros só fornecerem valores padrão.

Os parâmetros opcionais são uma alternativa: public Pizza(string size = "medium", string crust = "classic", int toppings = 0) dá um construtor que cobre as três chamadas. O encadeamento ainda se encaixa melhor quando os construtores mais curtos precisam calcular algo, ou quando mudar um padrão não pode exigir que quem chama recompile (os padrões dos parâmetros opcionais são copiados no código de quem chama em tempo de compilação).

Chamando um construtor da classe base com base(...)

Uma classe derivada não herda construtores. Todo construtor da classe derivada precisa executar antes um construtor da classe base. Se você não escrever nada, o compilador insere uma chamada ao construtor sem parâmetros da base; se a base não tiver um, você precisa escolher um com : base(...).

Vale a pena ver uma vez a ordem em que tudo executa:

Saída:

Truck field initializer
Vehicle field initializer
Vehicle constructor body
Truck constructor body
KL-204, 3 axles

Os inicializadores de campos executam primeiro, a classe derivada antes da classe base, e depois os corpos dos construtores executam da base para baixo. Então, quando o corpo de Truck executa, Plate já está definido. A única armadilha nessa ordem: se o construtor da base chama um método virtual que Truck sobrescreve, a sobrescrita executa antes do corpo do construtor de Truck e vê Axles ainda em 0. Evite chamar métodos virtuais em construtores.

Construtores static

Um construtor static inicializa o tipo em vez de um objeto. Ele não tem parâmetros nem modificador de acesso, e o runtime o chama exatamente uma vez, logo antes de o tipo ser usado pela primeira vez.

Saída:

Program started
Loading tax table (runs once)
123.00
119.00

O runtime torna o construtor static thread safe: mesmo que várias threads toquem o tipo ao mesmo tempo, ele executa uma vez. Se ele lançar uma exceção, todo uso posterior do tipo lança TypeInitializationException enquanto o processo existir, então mantenha-o simples e sem I/O que possa falhar.

Construtor vs inicializador de objeto

Um inicializador de objeto, new Pizza("large") { Toppings = 3 }, não é um segundo construtor. O compilador o transforma em "execute o construtor e depois atribua estes membros". Use cada um para o que ele faz bem:

  • Parâmetros do construtor para os valores sem os quais o objeto não pode ser válido. Todo chamador precisa passá-los, e o construtor pode verificá-los.
  • Inicializador para configurações opcionais com padrões razoáveis.
var order = new Order(customerId: 42) { Note = "Leave at the door", GiftWrap = true };

Desde o C# 11, uma propriedade marcada como required precisa ser definida no inicializador, o que dá à sintaxe de inicializador algumas das garantias do construtor. Isso é coberto na página de propriedades.

Construtores privados

Um construtor private significa que só a própria classe pode criar instâncias. Dois padrões o usam. Um método de fábrica static, em que a classe controla a criação e pode retornar um objeto em cache ou null, e um singleton:

Saída:

21
100

O erro de compilação para new Temperature(5m) fora da classe é o CS0122, o mesmo que os modificadores de acesso produzem para qualquer membro privado. Métodos de fábrica com nome resolvem uma limitação real: dois construtores não podem receber os dois um único decimal, mas FromCelsius e FromFahrenheit podem. Uma classe só com membros static (uma classe utilitária) deve ser declarada como static em vez de esconder o construtor.

Construtores com corpo de expressão

Um construtor cujo corpo é uma única instrução pode usar =>:

public Product(string name) => Name = name;

Com tuplas, você pode até atribuir vários campos em uma linha: public Point(int x, int y) => (X, Y) = (x, y);.

Construtores primários (C# 12)

O C# 12 permite que uma classe ou struct declare os parâmetros do construtor no próprio tipo. Os parâmetros ficam em escopo em todos os membros:

public class Customer(string name, int id)
{
    public string Name { get; } = name;          // copy into a property
    public string Label => $"#{id} {Name}";      // or read a parameter directly

    public Customer(string name) : this(name, 0) { }   // other constructors must chain to it
}

Duas diferenças em relação aos records surpreendem. Os parâmetros do construtor primário de uma classe não viram propriedades públicas; você mesmo os expõe. E um parâmetro usado dentro de um membro é capturado em um campo oculto que continua mutável, então name = "x"; dentro de um método compila. Construtores primários se encaixam melhor em injeção de dependência, em que os parâmetros são serviços que a classe só chama:

public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
    public Order Get(int id) => repo.Find(id);
}

Erros comuns

  • Perder o construtor sem parâmetros. Adicionar um construtor com parâmetros remove o implícito (CS7036 em cada new X()).
  • Esquecer o : base(...) quando a classe base não tem construtor sem parâmetros. O compilador não consegue inserir a chamada implícita e informa que não há argumento para o parâmetro obrigatório do construtor da base.
  • Chamar métodos virtuais em um construtor. A sobrescrita executa antes do corpo do construtor derivado.
  • Trabalho pesado em um construtor. Chamadas de rede, leituras de arquivo ou qualquer coisa lenta tornam os objetos caros e as exceções difíceis de tratar. Use um método de fábrica static, ou um método de inicialização async, para esse trabalho.
  • Escrever um tipo de retorno. public void Product() é um método comum chamado Product, e o compilador o rejeita porque um membro não pode ter o mesmo nome do tipo que o contém (CS0542).

Perguntas frequentes

O que é um construtor em C#?

Um construtor é um método especial que executa quando um objeto é criado com new. Ele tem o mesmo nome da classe e não tem tipo de retorno: public Product(string name) { Name = name; }. O trabalho dele é deixar o novo objeto em um estado válido, normalmente atribuindo campos a partir dos parâmetros e rejeitando entradas inválidas.

C# cria um construtor padrão automaticamente?

Só quando a classe não declara nenhum construtor. Nesse caso o compilador adiciona um construtor público sem parâmetros que deixa cada campo com o valor do inicializador ou o valor padrão. Assim que você escreve qualquer construtor, por exemplo um que recebe um nome, o implícito some e new Product() deixa de compilar (erro CS7036). Declare você mesmo public Product() { } se ainda precisar dele.

Como chamar um construtor a partir de outro em C#?

Use : this(...) depois da lista de parâmetros do construtor: public Product(string name) : this(name, 0m) { }. O construtor de destino executa primeiro, depois o corpo daquele que você chamou. Para chamar um construtor da classe base, use : base(...) do mesmo jeito.

O que é um construtor static em C#?

Um construtor marcado como static, sem parâmetros e sem modificador de acesso, que executa uma vez por tipo, antes de a primeira instância ser criada ou de o primeiro membro static ser usado. Ele serve para inicializar campos static que precisam de mais que um inicializador de uma linha. Você não pode chamá-lo, e se ele lançar uma exceção o tipo fica inutilizável pelo resto do programa (TypeInitializationException).

Devo usar um construtor ou um inicializador de objeto?

Use parâmetros do construtor para os valores sem os quais o objeto não pode ser válido, porque o compilador obriga todo chamador a passá-los e o construtor pode verificá-los. Use um inicializador de objeto (new Product("Mug") { Color = "blue" }) para configurações opcionais. O inicializador executa depois que o construtor termina.

O que são construtores primários no C# 12?

Parâmetros escritos na própria declaração da classe, class Customer(string name, int id) { ... }, disponíveis para todos os membros da classe. Ao contrário dos parâmetros posicionais de um record, eles não viram propriedades públicas: exponha-os explicitamente com public string Name => name; ou public string Name { get; } = name;.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR