Constructors

Constructors run when an object is built so it is never half-finished: overloads, this(...) chaining, and C# 12 primary constructors.

Syntaxpublic TypeName(int x) { /* body */ } // and: public TypeName() : this(0) { }

A constructor is a block of code that runs when an object is built. It carries the same name as the class and has no return type. Its job is to leave the new object in a sensible state, so no other code has to cope with a half-built one.

Declare no constructor and C# supplies a free parameterless one for a class. It applies any field initialisers and nothing more. Declare any constructor of your own and that free one is not supplied.

More than one

A class may declare several constructors, each with a different parameter list. Picking between methods that share a name by their arguments is called overloading. Rather than repeat the setup in each one, the short ones hand their work to the long one with the : this(...) initialiser, written between the parameter list and the body. The real work then lives in exactly one place.

Primary constructors

C# 12 added the primary constructor: the parameters go in parentheses after the class name, and are in scope throughout the body of the class. The compiler stores a parameter in a hidden private field only if something other than an initialiser uses it. A class with a primary constructor gets no free parameterless constructor, and any other constructor you write must chain to the primary one with : this(...).

What trips people up

Field initialisers run before the constructor body, in the order written, so a list created at its declaration is already usable on the first line of the body.

Example

Example · csharp
// Each `new` runs one constructor before you get the object back.
Coords origin = new Coords();
Coords point = new Coords(5, 3);

Console.WriteLine(origin.Describe());
Console.WriteLine(point.Describe());

class Coords
{
    private readonly int _x;
    private readonly int _y;

    // Two constructors share the name Coords and differ in their parameters.
    // This one does no work itself. It hands over to the other with this(...).
    public Coords() : this(0, 0)
    {
    }

    // The one real constructor. Every Coords ends up here, so there is
    // one place where the fields get set.
    public Coords(int x, int y)
    {
        _x = x;
        _y = y;
    }

    public string Describe()
    {
        return $"({_x},{_y})";
    }
}

// Output:
// (0,0)
// (5,3)

When to use it

  • Require the values an object cannot function without, so no caller can build a broken one.
  • Offer a convenient short constructor that forwards to the full one with sensible defaults.
  • Shrink a class that only carries data down to a primary constructor and a few properties.

More examples

Primary constructor next to the longhand

The primary constructor removes the field-plus-assignment boilerplate, which matters most for a class whose only job is to hold a few values given to it. One real difference: a primary constructor parameter is not readonly, so code inside the class may reassign it, whereas _reference above cannot be touched after construction. Where that matters, keep the longhand.

Example · csharp
Order shortForm = new Order("A-7", 3);
OrderLonghand longForm = new OrderLonghand("A-7", 3);

Console.WriteLine($"{shortForm.Reference} x{shortForm.Quantity}");
Console.WriteLine($"{longForm.Reference} x{longForm.Quantity}");

// C# 12 primary constructor. reference and quantity are parameters of the
// class itself and are in scope anywhere in its body.
class Order(string reference, int quantity)
{
    public string Reference => reference;
    public int Quantity => quantity;
}

// The same type written out. This is what the short form above amounts to.
class OrderLonghand
{
    private readonly string _reference;
    private readonly int _quantity;

    public OrderLonghand(string reference, int quantity)
    {
        _reference = reference;
        _quantity = quantity;
    }

    public string Reference => _reference;
    public int Quantity => _quantity;
}

// Output:
// A-7 x3
// A-7 x3

A primary constructor with a second constructor

Use this when a type has one real set of inputs and one common default. Leaving out the : this("pine", 25) part is a compile error rather than a silent bug, which is the point of the rule: the compiler will not let a constructor exist that skips the primary one and leaves its parameters unset.

Example · csharp
Crate standard = new Crate();
Crate custom = new Crate("oak", 40);

Console.WriteLine(standard.Label);
Console.WriteLine(custom.Label);

class Crate(string material, int litres)
{
    // A class with a primary constructor gets no free parameterless one,
    // so this is the only way to write new Crate(). It must chain to the
    // primary constructor, which is what guarantees material and litres
    // always have values.
    public Crate() : this("pine", 25)
    {
    }

    public string Label => $"{material} crate, {litres} litres";
}

// Output:
// pine crate, 25 litres
// oak crate, 40 litres

Field initialisers run first

Knowing the order lets you keep collections and other fixed collaborators out of the constructor entirely: initialise them at their declaration and the constructor only handles what the caller supplied. It also explains a real failure: if several constructors each build their own list, one of them will eventually forget, and the field initialiser form cannot.

Example · csharp
Session session = new Session("alice");

Console.WriteLine(session.Describe());

class Session
{
    // Field initialisers run before the constructor body, top to bottom.
    private readonly List<string> _events = new List<string>();
    private readonly string _user;

    public Session(string user)
    {
        _user = user;

        // _events is already a usable empty list by the time this runs,
        // so no null check and no second assignment are needed.
        _events.Add("opened");
    }

    public string Describe()
    {
        return $"{_user}: {string.Join(", ", _events)}";
    }
}

// Output:
// alice: opened

Discussion

  • Be the first to comment on this lesson.