Constructors are the setup crew that gets your objects ready to work.
Imagine you're building a house. Before anyone can live in it, you need to lay the foundation, put up walls, and install the plumbing. A constructor in C# is like the construction crew that prepares an object before it's ready to be used.
When you create a new object with new, the constructor runs automatically. It sets initial values, validates inputs, and ensures the object starts in a valid state.
In this lesson, you'll learn about default constructors, parameterized constructors, constructor overloading, constructor chaining, static constructors, and the modern primary constructors in C# 12.
A constructor is a special method that runs automatically when you create an object. It initialises the object's state and ensures it's ready to be used.
You don't call a constructor directly — it's invoked by the new keyword when you create an instance.
A constructor has the same name as the class and no return type (not even void). Its job is to set up the object.
You can have multiple constructors with different parameters — that's called overloading.
A constructor is a member of a class or struct that is executed when an instance is created. It initialises fields, runs validation, and can call base constructors. Constructors can be:
| Constructor Type | When It Runs | Common Use |
|---|---|---|
| Default (parameterless) | When you call new Class() |
Initialises fields to default values |
| Parameterized | When you call new Class(args) |
Sets specific values, validates input |
| Static | Once, before any instance or static member is accessed | Initialises static fields, runs one-time setup |
| Primary (C# 12) | When object is created | Concise constructor with parameter capture |
Without constructors, you'd have to manually set every field after creating an object:
var person = new Person();
person.Name = "Alice";
person.Age = 30;This is error-prone and tedious. Worse, you might forget to set a required field, leaving the object in an invalid state.
Also, there's no way to enforce that certain values must be provided. The object could be created but not fully initialised.
Constructors solve this by:
Here's how constructors fit into the object creation lifecycle:
new Person("Alice", 30)Let's trace different types of constructors step by step.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// Default constructor — provided automatically if you don't define one
public Person()
{
Name = "Unknown";
Age = 0;
}
}
var p = new Person(); // calls the default constructorIf you don't define any constructors, the compiler provides a default constructor that initialises fields to their default values.
public class Person
{
public string Name { get; }
public int Age { get; }
// Parameterized constructor
public Person(string name, int age)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name is required.", nameof(name));
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age), "Age must be between 0 and 150.");
Name = name;
Age = age;
}
}
var p = new Person("Alice", 30); // calls parameterized constructorParameters force the caller to provide required data. Validation ensures the object is created in a valid state.
public class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name) : this(name, 0) // chains to the other constructor
{
}
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
var p1 = new Person("Alice"); // name only, age defaults to 0
var p2 = new Person("Bob", 25); // name and ageOverloading gives flexibility: you can create objects with different sets of parameters.
public class DatabaseConnection
{
private static string ConnectionString;
// Static constructor — runs once before any instance or static member is accessed
static DatabaseConnection()
{
ConnectionString = LoadFromConfiguration();
Console.WriteLine("Static constructor called.");
}
private static string LoadFromConfiguration()
{
// Simulate loading from appsettings.json
return "Server=localhost;Database=MyDb;";
}
public static void Connect()
{
Console.WriteLine($"Connecting using {ConnectionString}");
}
}
DatabaseConnection.Connect(); // static constructor runs firstStatic constructors are perfect for one-time setup — loading config, initialising static fields, etc.
// Primary constructor — parameters become fields/properties
public class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
public void Introduce()
{
Console.WriteLine($"Hi, I'm {Name} and I'm {Age}.");
}
}
var p = new Person("Alice", 30);
p.Introduce();Primary constructors (C# 12) let you define a constructor and capture parameters in a concise way. They're useful for simple data-holding types.
Let's build a Product class with multiple constructors, validation, and a static constructor.
public class Product
{
// ─── Static fields and constructor ───
private static int _nextId = 1000;
private static readonly string _defaultCategory;
static Product()
{
// Static constructor — runs once
_defaultCategory = "General";
Console.WriteLine($"Static constructor: Default category set to '{_defaultCategory}'.");
}
// ─── Instance fields ───
private readonly int _id;
private string _name;
private decimal _price;
private string _category;
// ─── Properties ───
public int Id => _id;
public string Name => _name;
public decimal Price => _price;
public string Category => _category;
// ─── Constructors ───
// Parameterless constructor — uses default category
public Product() : this("Untitled", 0m, _defaultCategory)
{
}
// Constructor with name and price, uses default category
public Product(string name, decimal price) : this(name, price, _defaultCategory)
{
}
// Full parameterized constructor — with validation
public Product(string name, decimal price, string category)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Product name is required.", nameof(name));
if (price < 0)
throw new ArgumentException("Price cannot be negative.", nameof(price));
if (string.IsNullOrWhiteSpace(category))
throw new ArgumentException("Category is required.", nameof(category));
_id = Interlocked.Increment(ref _nextId);
_name = name.Trim();
_price = price;
_category = category.Trim();
}
public void Display()
{
Console.WriteLine($"ID: {Id} | {Name} | {Price:C} | Category: {Category}");
}
}
// ─── Usage ───
var p1 = new Product(); // uses default category
p1.Display(); // ID: 1001 | Untitled | $0.00 | Category: General
var p2 = new Product("Laptop", 1200.99m);
p2.Display(); // ID: 1002 | Laptop | $1,200.99 | Category: General
var p3 = new Product("Chair", 89.50m, "Furniture");
p3.Display(); // ID: 1003 | Chair | $89.50 | Category: Furniture
try
{
var invalid = new Product("", -10m, "");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Validation error: {ex.Message}");
}Code → Meaning → Result
_defaultCategory.this(..., ...) calls the main constructor with default values.In a real application, you might have a Customer class with a constructor that sets up dependencies, validates data, and initialises collections.
public class Customer
{
private readonly List<Order> _orders = new();
private readonly ILogger _logger;
public int Id { get; }
public string Name { get; private set; }
public string Email { get; private set; }
public DateTime CreatedAt { get; }
public IReadOnlyList<Order> Orders => _orders;
// Primary constructor (C# 12) — captures dependencies
public Customer(int id, string name, string email, ILogger logger)
{
Id = id;
Name = name ?? throw new ArgumentNullException(nameof(name));
Email = email ?? throw new ArgumentNullException(nameof(email));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
CreatedAt = DateTime.UtcNow;
_logger.LogInfo($"Customer created: {Id} - {Name}");
}
public void PlaceOrder(Order order)
{
if (order == null) throw new ArgumentNullException(nameof(order));
_orders.Add(order);
_logger.LogInfo($"Order {order.Id} placed for customer {Id}.");
}
public void UpdateEmail(string newEmail)
{
if (string.IsNullOrWhiteSpace(newEmail) || !newEmail.Contains('@'))
throw new ArgumentException("Valid email required.", nameof(newEmail));
var oldEmail = Email;
Email = newEmail;
_logger.LogInfo($"Email changed from {oldEmail} to {newEmail} for customer {Id}.");
}
}
// Usage in a real application (with dependency injection)
// var logger = new ConsoleLogger();
// var customer = new Customer(101, "Alice", "alice@example.com", logger);
// customer.PlaceOrder(new Order(5001, customer.Id));
// Console.WriteLine($"Customer {customer.Name} has {customer.Orders.Count} orders.");This example shows how constructors:
ILogger is provided from outside._orders is a private list, exposed as read-only.When a car is built at a factory, it goes through an assembly line. Each car starts as an empty shell, then components are installed (engine, wheels, seats). At the end, it's a complete, drivable car.
A constructor is like that assembly line:
You can have different assembly lines (overloaded constructors) for different car configurations.
What happens inside the .NET runtime when you call new?
this() or base(), those are executed first.A constructor is not a method. You can't call it directly (except with new). It has no return type, and its name must match the class name. It's used for initialisation, not for behaviour.
If you don't define any constructors in a class, the compiler generates a parameterless default constructor that does nothing (but initialises fields to default values).
If you define any constructor (even a parameterized one), the compiler does not generate a default constructor. You'd have to define one explicitly if needed.
public class Example
{
public Example(int x) { } // No default constructor
}
// var e = new Example(); // Error! No default constructorthis() vs base() in constructorsthis(...) — calls another constructor in the same class (constructor chaining).base(...) — calls a constructor in the base class.Primary constructors (C# 12) are a shorthand for declaring a constructor and capturing parameters. They're useful for simple types, but they have limitations (you can't add extra logic in the constructor body unless you use a full constructor). You can mix them: define a primary constructor and also provide a full constructor that calls it.
Wrong:
public class Person
{
public string Name { get; set; } // No constructor — but fields are mutable
}
var p = new Person();
// p.Name is null — invalid state if Name is requiredCorrect:
public class Person
{
public string Name { get; }
public Person(string name) => Name = name;
}
var p = new Person("Alice"); // Must provide nameWrong:
public Person(string name, int age)
{
Name = name; // Could be null or empty
Age = age; // Could be negative
}Correct:
public Person(string name, int age)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException(...);
if (age < 0 || age > 150) throw new ArgumentOutOfRangeException(...);
Name = name;
Age = age;
}If you define a parameterized constructor, the default constructor is not generated. If you still want a parameterless constructor, you must define it explicitly.
public class Product
{
public Product(string name) { ... } // No default constructor
}
// var p = new Product(); // Error!Static constructors can slow down type initialisation if they do heavy work. They also can cause deadlocks if they wait on other threads.
Use static constructors for simple initialisation (e.g., reading a config value). Avoid heavy I/O or blocking operations.
this) = "I'll call another constructor to do the heavy lifting"readonly fields
new.this() to avoid code duplication.You've seen how constructors initialise objects. Let's test your knowledge.
1. Which of the following statements about constructors in C# is correct?
Correct: C
Why C is correct: Constructor overloading allows a class to have multiple constructors with different signatures (different number or types of parameters). This provides flexibility in how objects are created.
Why A is incorrect: Constructors have no return type — not even void.
Why B is incorrect: Constructors are called automatically by the runtime when new is used. You cannot call them directly like methods.
Why D is incorrect: Constructors are not inherited. However, a derived class constructor can call a base class constructor using base(...).
Reinforcement: Overloading is a key feature of constructors, allowing multiple ways to create an object.
2. If you define a parameterized constructor in a class, what happens to the default (parameterless) constructor?
Correct: B
Why B is correct: In C#, if you define any constructor (parameterized or not), the compiler does not generate a default parameterless constructor. If you need one, you must define it explicitly.
Why A is incorrect: The default constructor is only provided if you define no constructors at all.
Why C is incorrect: The default constructor is simply not generated; it doesn't become private.
Why D is incorrect: Constructors are instance members, not static.
Reinforcement: Adding any constructor removes the implicit default constructor. Be mindful if you need a parameterless version.
3. What is the purpose of constructor chaining using this(...)?
Correct: B
Why B is correct: Constructor chaining using this(...) allows one constructor to call another constructor in the same class. This is useful to avoid duplicating initialisation logic.
Why A is incorrect: this(...) calls another constructor, not a method.
Why C is incorrect: That's the purpose of base(...).
Why D is incorrect: A private constructor is defined with the private keyword.
Reinforcement: Chaining helps keep your code DRY (Don't Repeat Yourself).
4. When does a static constructor run?
Correct: B
Why B is correct: A static constructor runs exactly once per type (per AppDomain). It is executed before any static member is accessed or any instance of the type is created. This makes it ideal for one-time initialisation.
Why A is incorrect: Static constructors run once, not per instance.
Why C is incorrect: Garbage collection does not trigger static constructors.
Why D is incorrect: The static constructor runs on demand, not necessarily at application startup. It runs the first time the type is referenced.
Reinforcement: Static constructors are perfect for loading configuration, initialising static fields, or setting up a shared resource.
5. What is the output of the following code?
public class Test
{
public Test() => Console.Write("Instance ");
static Test() => Console.Write("Static ");
}
var t1 = new Test();
var t2 = new Test(); Correct: A — Static Instance Instance
Why A is correct: The static constructor runs once, before any instance constructor. When t1 is created, the static constructor runs first (prints "Static"), then the instance constructor for t1 (prints "Instance"). When t2 is created, the static constructor does not run again, and only the instance constructor runs (prints "Instance"). So output is "Static Instance Instance".
Why B is incorrect: The static constructor runs before any instance constructor, not after.
Why C is incorrect: It would print only one "Instance" but we create two objects.
Why D is incorrect: The static constructor runs only once, and it runs before the first instance constructor.
Reinforcement: Static constructors run once and before any instance constructors. The order is static constructor → instance constructor(s).
You now have a solid understanding of constructors — the setup crew that gets your objects ready for action!
dotnetmadeeasy.com — Learn C# and .NET, the right way.