Constructors
A constructor is a special method that runs when an object is created. It sets the object up in a valid state — filling in required data and checking it — so the rest of your code never sees a half-built object. C# also offers primary constructors and required members for concise, safe initialisation.
Writing a constructor
A constructor has the same name as the class and no return type. Its parameters become the arguments to new:
var acct = new BankAccount("Rosa Diaz", 250m); acct.Deposit(100m); Console.WriteLine(acct.Summary()); class BankAccount { private string _owner; private decimal _balance; public BankAccount(string owner, decimal openingBalance) { _owner = owner; _balance = openingBalance; } public void Deposit(decimal amount) => _balance += amount; public string Summary() => $"{_owner}: {_balance:C}"; }
Rosa Diaz: $350.00
Once a class declares a constructor with parameters, new BankAccount() with no arguments no longer compiles — callers must supply an owner and a balance. That's the point: an account can't exist without them.
var acct = new BankAccount(); class BankAccount { public BankAccount(string owner) { } }
Program.cs(1,16): error CS7036: There is no argument given that corresponds to the required parameter 'owner' of 'BankAccount.BankAccount(string)'
A class with no constructor gets an invisible parameterless one, which is why new Dog() worked in the last lesson.
Validating in the constructor
The constructor is the place to reject bad data. Throwing an exception means an invalid object is never created:
var ok = new Temperature(21.5); Console.WriteLine(ok.Celsius); var bad = new Temperature(-300); class Temperature { public double Celsius; public Temperature(double celsius) { if (celsius < -273.15) throw new ArgumentOutOfRangeException(nameof(celsius), "Below absolute zero."); Celsius = celsius; } }
21.5 Unhandled exception. System.ArgumentOutOfRangeException: Below absolute zero. (Parameter 'celsius')
nameof(celsius) produces the text "celsius"; if you rename the parameter, the message updates automatically.
this
Inside a class, this refers to the current object. You need it when a parameter has the same name as a field:
var c = new Circle(2); Console.WriteLine(c.Area()); class Circle { private double radius; public Circle(double radius) { this.radius = radius; // field = parameter } public double Area() => Math.Round(Math.PI * radius * radius, 2); }
12.57
Many C# codebases avoid the clash by naming private fields with an underscore (_radius), as in the bank account example.
Several constructors
Constructors can be overloaded. One constructor can call another with : this(...), so the real setup logic lives in one place:
var a = new Duration(90); var b = new Duration(1, 30, 0); var c = new Duration(); Console.WriteLine($"{a} | {b} | {c}"); class Duration { private readonly int _totalSeconds; public Duration(int totalSeconds) { ArgumentOutOfRangeException.ThrowIfNegative(totalSeconds); _totalSeconds = totalSeconds; } public Duration(int hours, int minutes, int seconds) : this(hours * 3600 + minutes * 60 + seconds) { } public Duration() : this(0) { } public override string ToString() => $"{_totalSeconds / 3600}h {_totalSeconds % 3600 / 60}m {_totalSeconds % 60}s"; }
0h 1m 30s | 1h 30m 0s | 0h 0m 0s
A readonly field can only be assigned in its declaration or in a constructor — after that it never changes.
Primary constructors
Since C# 12, parameters can go right after the class name. These primary constructor parameters are available throughout the class body, which removes a lot of boilerplate when a constructor only stores its arguments:
var item = new OrderLine("Desk lamp", 24.99m, 2); Console.WriteLine(item.Describe()); Console.WriteLine(item.Total); class OrderLine(string product, decimal unitPrice, int quantity) { public decimal Total => unitPrice * quantity; public string Describe() => $"{quantity} x {product} @ {unitPrice:C}"; }
2 x Desk lamp @ $24.99 49.98
Primary constructor parameters aren't fields or properties — they're parameters that stay in scope. If you want to validate them or expose them publicly, assign them to members:
var user = new User(" ada@example.com "); Console.WriteLine($"[{user.Email}]"); class User(string email) { public string Email { get; } = string.IsNullOrWhiteSpace(email) ? throw new ArgumentException("Email is required.", nameof(email)) : email.Trim().ToLowerInvariant(); }
[ada@example.com]
(The { get; } syntax declares a read-only property — next lesson.) Records (lesson 40) take primary constructors further, turning the parameters into public properties automatically.
Required members
Object initialisers are pleasant to read, but nothing forces callers to set every important member. Marking a property required makes the compiler insist on it:
var p = new Person { FirstName = "Ada" }; class Person { public required string FirstName { get; init; } public required string LastName { get; init; } public int? BirthYear { get; init; } }
Program.cs(1,13): error CS9035: Required member 'Person.LastName' must be set in the object initializer or attribute constructor.
var p = new Person { FirstName = "Ada", LastName = "Lovelace", BirthYear = 1815 }; Console.WriteLine($"{p.FirstName} {p.LastName} ({p.BirthYear})"); class Person { public required string FirstName { get; init; } public required string LastName { get; init; } public int? BirthYear { get; init; } }
Ada Lovelace (1815)
Choosing an approach
- Regular constructor — when creating the object involves validation or logic.
- Primary constructor — for classes whose constructor mostly captures dependencies or values, such as services.
- required + init properties — for data objects with many optional members, where named initialisers read best.
Try it yourself
Add a second constructor to Rgb that takes a hex string like "#FF8800" and chains to the first.
rgb(255, 136, 0) rgb(0, 128, 128)