Construtores de instâncias (Guia de Programação em C#)

Você declara um construtor de instância para especificar o código executado ao criar uma nova instância de um tipo com a expressão new. Para inicializar uma classe estática ou variáveis estáticas em uma classe não estática, é possível definir um construtor estático.

Como mostra o exemplo a seguir, você pode declarar vários construtores de instância em um tipo:

class Coords
{
    public Coords()
        : this(0, 0)
    {  }

    public Coords(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }

    public override string ToString() => $"({X},{Y})";
}

class Example
{
    static void Main()
    {
        var p1 = new Coords();
        Console.WriteLine($"Coords #1 at {p1}");
        // Output: Coords #1 at (0,0)

        var p2 = new Coords(5, 3);
        Console.WriteLine($"Coords #2 at {p2}");
        // Output: Coords #2 at (5,3)
    }
}

No exemplo anterior, o primeiro construtor, sem parâmetros, chama o segundo construtor com ambos os argumentos iguais a 0. Para fazer isso, use a palavra-chave this.

Ao declarar um construtor de instância em uma classe derivada, você pode chamar um construtor de uma classe base. Para fazer isso, use a palavra-chave base, como mostra o exemplo a seguir:

abstract class Shape
{
    public const double pi = Math.PI;
    protected double x, y;

    public Shape(double x, double y)
    {
        this.x = x;
        this.y = y;
    }

    public abstract double Area();
}

class Circle : Shape
{
    public Circle(double radius)
        : base(radius, 0)
    {  }

    public override double Area() => pi * x * x;
}

class Cylinder : Circle
{
    public Cylinder(double radius, double height)
        : base(radius)
    {
        y = height;
    }

    public override double Area() => (2 * base.Area()) + (2 * pi * x * y);
}

class Example
{
    static void Main()
    {
        double radius = 2.5;
        double height = 3.0;

        var ring = new Circle(radius);
        Console.WriteLine($"Area of the circle = {ring.Area():F2}");
        // Output: Area of the circle = 19.63
        
        var tube = new Cylinder(radius, height);
        Console.WriteLine($"Area of the cylinder = {tube.Area():F2}");
        // Output: Area of the cylinder = 86.39
    }
}

Construtores sem parâmetros

Se uma classe não tiver construtores de instância explícita, o C# fornecerá um construtor sem parâmetros que você pode usar para instanciar uma instância dessa classe, como mostra o exemplo a seguir:

public class Person
{
    public int age;
    public string name = "unknown";
}

class Example
{
    static void Main()
    {
        var person = new Person();
        Console.WriteLine($"Name: {person.name}, Age: {person.age}");
        // Output:  Name: unknown, Age: 0
    }
}

Esse construtor inicializa os campos e as propriedades da instância de acordo com os inicializadores correspondentes. Se um campo ou propriedade não tiver um inicializador, seu valor será definido como o valor padrão do tipo do campo ou da propriedade. Se você declarar pelo menos um construtor de instância em uma classe, o C# não fornecerá um construtor sem parâmetros.

Um tipo de estrutura sempre fornece um construtor sem parâmetros. O construtor sem parâmetros é um construtor sem parâmetros implícito que produz o valor padrão de um tipo ou um construtor sem parâmetros explicitamente declarado. Para obter mais informações, consulte a seção Inicialização de struct e valores padrão do artigo Tipos de estrutura.

Construtores primários

A partir do C# 12, você pode declarar um construtor primário em classes e structs. Você coloca todos os parâmetros entre parênteses seguindo o nome do tipo:

public class NamedItem(string name)
{
    public string Name => name;
}

Os parâmetros para um construtor primário estão no escopo em todo o corpo do tipo de declaração. Eles podem inicializar propriedades ou campos. Eles podem ser usados como variáveis em métodos ou funções locais. Eles podem ser passados para um construtor base.

Um construtor primário indica que esses parâmetros são necessários para qualquer instância do tipo. Qualquer construtor escrito explicitamente deve usar a sintaxe do inicializador this(...) para invocar o construtor primário. Isso garante que os parâmetros do construtor primário sejam definitivamente atribuídos por todos os construtores. Para qualquer tipo class, incluindo tipos record class, o construtor implícito sem parâmetros não é emitido quando um construtor primário está presente. Para qualquer tipo struct, incluindo tipos record struct, o construtor implícito sem parâmetros sempre é emitido e sempre inicializa todos os campos, incluindo parâmetros de construtor primário, para o padrão de 0 bit. Se você escrever um construtor explícito sem parâmetros, ele deverá invocar o construtor primário. Nesse caso, você pode especificar um valor diferente para os parâmetros do construtor primário. O código a seguir mostra exemplos de construtores primários.

// name isn't captured in Widget.
// width, height, and depth are captured as private fields
public class Widget(string name, int width, int height, int depth) : NamedItem(name)
{
    public Widget() : this("N/A", 1,1,1) {} // unnamed unit cube

    public int WidthInCM => width;
    public int HeightInCM => height;
    public int DepthInCM => depth;

    public int Volume => width * height * depth;
}

Você pode adicionar atributos ao método de construtor primário sintetizado especificando o destino method: no atributo:

[method: MyAttribute]
public class TaggedWidget(string name)
{
   // details elided
}

Se você não especificar o destino method, o atributo será colocado na classe em vez do método.

Nos tipos class e struct, os parâmetros do construtor primário estão disponíveis em qualquer lugar no corpo do tipo. Eles podem ser usados como campos de membro. Quando um parâmetro de construtor primário é usado, o compilador captura o parâmetro de construtor em um campo privado com um nome gerado pelo compilador. Se um parâmetro de construtor primário não for usado no corpo do tipo, nenhum campo privado será capturado. Essa regra impede a alocação acidental de duas cópias de um parâmetro de construtor primário que é passado para um construtor base.

Se o tipo incluir o modificador record, o compilador sintetizará uma propriedade pública com o mesmo nome que o parâmetro do construtor primário. Para tipos record class, se um parâmetro de construtor primário usar o mesmo nome que um construtor primário base, essa propriedade será uma propriedade pública do tipo base record class. Ele não é duplicado no tipo derivado record class. Essas propriedades não são geradas para tipos nãorecord.

Confira também