← Open in the full interactive course (progress tracking, search & more)

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.

What Is It?

The Simple Explanation

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.

Constructor = Object Initialiser

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.

The Technical Definition

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

Why Does It Exist?

The Problem

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.

The Solution

Constructors solve this by:

Big Picture

Here's how constructors fit into the object creation lifecycle:

CONSTRUCTOR LIFECYCLE
SOURCE
new Person("Alice", 30)
RUNTIME
allocates memory + runs constructor
OBJECT
fully initialised and ready to use
Inside the constructor:
Validate parameters
Set fields / properties
Call base constructor (if any)

How It Works

Let's trace different types of constructors step by step.

CONSTRUCTOR TYPES — STEP BY STEP
1. Default (parameterless) constructor
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 constructor

If you don't define any constructors, the compiler provides a default constructor that initialises fields to their default values.

2. Parameterized constructor
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 constructor

Parameters force the caller to provide required data. Validation ensures the object is created in a valid state.

3. Constructor overloading (multiple constructors)
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 age

Overloading gives flexibility: you can create objects with different sets of parameters.

4. Static constructor
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 first

Static constructors are perfect for one-time setup — loading config, initialising static fields, etc.

5. Primary constructor (C# 12)
// 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.

Simple Example

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

Real-World Example

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:

Analogy

Constructor = Car Factory Assembly Line

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.

Under the Hood

What happens inside the .NET runtime when you call new?

CONSTRUCTOR EXECUTION — INTERNAL VIEW
1. MEMORY ALLOCATION
2. STATIC CONSTRUCTOR (if any)
3. INSTANCE CONSTRUCTOR EXECUTION
4. OBJECT REFERENCE RETURNED

Common Confusion

1. Constructor vs Method

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.

2. Default Constructor — When Is It Provided?

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 constructor

3. this() vs base() in constructors

4. Primary Constructor vs Explicit Constructor

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.

Common Mistakes

Mistake 1 — Forgetting to define a constructor when you need one

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 required

Correct:

public class Person { public string Name { get; } public Person(string name) => Name = name; } var p = new Person("Alice"); // Must provide name

Mistake 2 — Not validating parameters

Wrong:

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; }

Mistake 3 — Using a parameterized constructor but still allowing default instantiation

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!

Mistake 4 — Overusing static constructors

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.

When Should I Use It?

Use constructors when:
Avoid constructors when:

Mental Model

Constructor = the object's birth and preparation
Default constructor = "give me a basic object"
Parameterized constructor = "here's what I need to build it"
Overloading = "I can build it in different ways"
Chaining (this) = "I'll call another constructor to do the heavy lifting"
Static constructor = "one-time setup for the whole type"
Primary constructor = "concise way to capture parameters"

Remember:
· Constructors run once per object (except static, which runs once per type)
· They ensure valid state
· They're the only place to set readonly fields

Key Takeaway


Check Your Understanding

You've seen how constructors initialise objects. Let's test your knowledge.

1. Which of the following statements about constructors in C# is correct?

Show answer

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?

Show answer

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(...)?

Show answer

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?

Show answer

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();
Show answer

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.