<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Object-oriented programming
All lessons in this course

Lesson 18 of 60 14 minIntermediate

Constructors and object initialisers

Make sure every object starts in a valid state: constructors, overloads, chaining with this(...), initialisers and required members.

In the last lesson we created a Beam and then set its fields one by one. Forget one line and you have a beam with length 0. A constructor is a special method that runs automatically when an object is created, so it can set everything up correctly from the start.

Writing a constructor

Beam.cs
public class Beam{    public string Profile;    public double Length;     // constructor: same name as the class, no return type    public Beam(string profile, double length)    {        Profile = profile;        Length = length;    }} Beam b = new Beam("IPE300", 6.0);      // arguments go to the constructorConsole.WriteLine($"{b.Profile} {b.Length}");
Output
IPE300 6
  • Its name is exactly the class name and it has no return type (not even void).
  • It runs once, when new is used.
  • If you write no constructor, C# gives you an invisible parameterless one. As soon as you write any constructor, that free one disappears.

Overloading and chaining

A class can have several constructors with different parameters. To avoid repeating setup code, one constructor can call another with : this(...). Step through how new runs them:

Interactive

What happens inside new

Beam.cs
class Beam{    public string Profile = "IPE200";   // field initializer    public double Length;     public Beam() : this(6.0) { }     public Beam(double length) : this(length, "IPE200") { }     public Beam(double length, string profile)    {        Length = length;        Profile = profile;    }}
The new object (heap)
Profilenull
Length0
Step 1 / 6
new Beam(8, "HEA300"): first the runtime reserves memory for a Beam on the heap. Every field starts at its default value: null for string, 0 for double.

The order to remember: memory is allocated with default values → field initializers run → the base class constructor runs → constructor bodies run, the innermost (the one doing the real work) first.

Validating in the constructor

Validate.cs
public Beam(string profile, double length){    if (length <= 0)        throw new ArgumentOutOfRangeException(nameof(length), "Length must be positive.");    Profile = profile;    Length = length;}

If a constructor throws, no object is created. So it is impossible to end up with an invalid beam. This is a big advantage over setting fields afterwards.

Object initialisers

For fields or properties that are optional, an object initialiser sets them right after the constructor, in one expression:

Initialiser.cs
var b = new Beam("IPE200", 5.0) { Mark = "B12", Level = 2 }; // with a parameterless constructor you can skip the bracketsvar p = new Point { X = 3, Y = 4 };

Primary constructors (C# 12)

Primary.cs
public class Beam(string profile, double length){    public string Profile { get; } = profile;    public double Length { get; } = length;} var b = new Beam("HEA200", 3.2);

The parameters are written right after the class name and can be used anywhere in the class. This saves a lot of boilerplate for simple classes.

required members (C# 11)

Required.cs
public class Column{    public required string Mark { get; init; }    public double Height { get; init; } = 3.0;} var c = new Column { Mark = "C1" };   // OK// var d = new Column();              // error CS9035: Mark must be set
Quick check

What is the return type of a constructor?

Quick check

What does : this(6.0) after a constructor’s parameter list do?

Practice

Create a class BankAccount with Owner (string) and Balance (decimal). Add a constructor that requires the owner and an optional opening balance (default 0) that must not be negative (throw ArgumentException otherwise). Create two accounts and print them.

Show the solution
Solution.cs
public class BankAccount{    public string Owner;    public decimal Balance;     public BankAccount(string owner, decimal openingBalance = 0)    {        if (openingBalance < 0)            throw new ArgumentException("Opening balance cannot be negative.");        Owner = owner;        Balance = openingBalance;    }} var a = new BankAccount("Ravi");var b = new BankAccount("Selva", 5000m);Console.WriteLine($"{a.Owner}: {a.Balance}");Console.WriteLine($"{b.Owner}: {b.Balance}");
Output
Ravi: 0
Selva: 5000

Remember

  • A constructor has the class’s name, no return type, and runs on new.
  • Writing any constructor removes the default parameterless one.
  • Chain overloads with : this(...) to avoid duplicate setup code.
  • Validate in the constructor so invalid objects can never exist.
0 of 60 lessons completed