C# Constructors
A constructor is a special member that runs when you create an object with new. Its job is to put the new object into a usable, valid starting state before the rest of your code can work with it. Constructors matter because classes usually have rules: a bank account needs an owner, a product needs a price, and an employee needs a name before those objects make sense.
Overview: How Constructors Work
A C# constructor has the same name as its class and has no return type, not even void. When code executes new Customer(), the runtime allocates memory for the object, initializes fields to their default values, runs field initializers, runs the selected constructor, and then returns a reference to the finished object.
Constructors are part of object-oriented design because they protect invariants. An invariant is a rule that should always be true for an object, such as Balance not starting below zero or Name not being empty. Instead of creating an object and hoping other code fills in the important properties later, a constructor can require the needed values up front.
If you do not write any constructor, the C# compiler provides a public parameterless constructor for a normal class. This is often called the default constructor. The moment you write your own constructor, the compiler stops generating that public parameterless constructor. If callers still need new Type(), you must write that constructor yourself.
Constructors can be overloaded, which means a class can provide more than one constructor with different parameter lists. C# chooses the constructor by the arguments passed to new, using the same overload resolution rules used for methods. Constructors can also chain to another constructor in the same class with : this(...), or call a base class constructor with : base(...).
Constructors are not inherited. A derived class must choose how the base part of the object is initialized. If you do not explicitly write a base(...) call, C# tries to call the base class parameterless constructor automatically. If the base class does not have one, the derived constructor must call an available base constructor explicitly.
Syntax
using System;
class Product
{
public string Name { get; }
public decimal Price { get; }
public Product() : this("Unnamed", 0m)
{
}
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
}
class Program
{
static void Main()
{
}
}
| Part | Meaning |
|---|---|
public Product() |
A parameterless constructor. It allows new Product(). |
public Product(string name, decimal price) |
A parameterized constructor. It requires values when the object is created. |
: this("Unnamed", 0m) |
Constructor chaining. The parameterless constructor reuses another constructor in the same class. |
Name = name; |
Initialization. Constructors commonly assign fields and get-only properties. |
Examples
Default and Parameterized Constructors
using System;
class Book
{
public string Title { get; }
public string Author { get; }
public Book()
{
Title = "Untitled";
Author = "Unknown";
}
public Book(string title, string author)
{
Title = title;
Author = author;
}
public void Print()
{
Console.WriteLine($"{Title} by {Author}");
}
}
class Program
{
static void Main()
{
Book draft = new Book();
Book novel = new Book("The Hobbit", "J. R. R. Tolkien");
draft.Print();
novel.Print();
}
}
Output:
Untitled by Unknown
The Hobbit by J. R. R. Tolkien
This class offers two ways to create a Book. The parameterless constructor supplies fallback values, while the parameterized constructor records real data. The properties are get-only, so they can be assigned in the constructor and then protected from accidental changes later.
Constructor Chaining and Validation
using System;
class BankAccount
{
public string AccountNumber { get; }
public string Owner { get; }
public decimal Balance { get; private set; }
public BankAccount() : this("TEMP", "Guest", 0m)
{
}
public BankAccount(string accountNumber, string owner, decimal openingBalance)
{
if (string.IsNullOrWhiteSpace(accountNumber))
{
throw new ArgumentException("Account number is required.");
}
if (string.IsNullOrWhiteSpace(owner))
{
throw new ArgumentException("Owner is required.");
}
if (openingBalance < 0m)
{
throw new ArgumentOutOfRangeException(nameof(openingBalance));
}
AccountNumber = accountNumber;
Owner = owner;
Balance = openingBalance;
}
public void PrintSummary()
{
Console.WriteLine($"Account {AccountNumber} opened for {Owner}");
Console.WriteLine($"Balance: {Balance:F2}");
}
}
class Program
{
static void Main()
{
BankAccount primary = new BankAccount("A-1001", "Maya", 250m);
BankAccount temporary = new BankAccount();
primary.PrintSummary();
temporary.PrintSummary();
}
}
Output:
Account A-1001 opened for Maya
Balance: 250.00
Account TEMP opened for Guest
Balance: 0.00
The parameterless constructor delegates to the full constructor, so there is one central place for validation and assignment. This avoids duplicated setup code and makes it harder for different constructors to create inconsistent objects.
Calling a Base Class Constructor
using System;
class Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
Console.WriteLine($"Person constructor: {Name}");
}
}
class Employee : Person
{
public string JobTitle { get; }
public Employee(string name, string jobTitle) : base(name)
{
JobTitle = jobTitle;
Console.WriteLine($"Employee constructor: {JobTitle}");
}
public void PrintCard()
{
Console.WriteLine($"{Name} works as {JobTitle}");
}
}
class Program
{
static void Main()
{
Employee employee = new Employee("Jordan", "Developer");
employee.PrintCard();
}
}
Output:
Person constructor: Jordan
Employee constructor: Developer
Jordan works as Developer
The base class constructor runs before the derived class constructor body. That order matters because the derived object includes a base object portion, and the base portion must be initialized first.
How Constructors Work Step by Step
- The expression
new Employee("Jordan", "Developer")asks the CLR to allocate enough memory for anEmployeeobject. - All fields are first set to their default values: references become
null, numbers become0, and booleans becomefalse. - Instance field initializers run from the most-derived type after base initialization rules are applied by the compiler-generated constructor sequence.
- The base class constructor runs. If you wrote
: base(name), that exact constructor is used. - The derived constructor body runs, assigning derived fields and properties.
- The finished reference is returned to the caller. Until the constructor completes, the object should be treated as still under construction.
For reference types such as classes, new usually creates an object on the managed heap and returns a reference. For value types such as structs, constructors also exist, but the storage location depends on how the value is used. This lesson focuses on class constructors, which are the most common OOP constructor use case.
Common Mistakes
Expecting the Compiler to Keep the Parameterless Constructor
class Customer
{
public string Name { get; }
public Customer(string name)
{
Name = name;
}
}
class Program
{
static void Main()
{
Customer customer = new Customer();
}
}
This does not compile because defining Customer(string name) removes the compiler-generated parameterless constructor. Fix it by calling the constructor that exists, or by writing a parameterless constructor intentionally.
using System;
class Customer
{
public string Name { get; }
public Customer() : this("Guest")
{
}
public Customer(string name)
{
Name = name;
}
}
class Program
{
static void Main()
{
Customer customer = new Customer();
Console.WriteLine($"Customer: {customer.Name}");
}
}
Output:
Customer: Guest
Assigning Readonly State Outside the Constructor
class Sensor
{
public readonly string Id;
public Sensor(string id)
{
Id = id;
}
public void Rename(string newId)
{
Id = newId;
}
}
A readonly field may be assigned in a field initializer or constructor, but not later in an ordinary method. If an identity should never change, create a new object when a different identity is needed.
using System;
class Sensor
{
public readonly string Id;
public Sensor(string id)
{
Id = id;
}
}
class Program
{
static void Main()
{
Sensor original = new Sensor("S-10");
Sensor replacement = new Sensor("S-11");
Console.WriteLine(original.Id);
Console.WriteLine(replacement.Id);
}
}
Output:
S-10
S-11
Best Practices
- Use constructors to require the minimum data needed for a valid object.
- Validate constructor arguments immediately and throw clear exceptions for invalid values.
- Prefer constructor chaining with
this(...)when overloads share setup logic. - Keep constructors focused. Heavy I/O, network calls, and long-running work are usually better placed in factory methods or services.
- Use get-only properties or
readonlyfields for values that should be fixed after construction. - Do not call virtual methods from constructors. Derived class state may not be initialized yet.
- When inheriting, make the required base initialization explicit with
base(...).
Practice Exercises
- Create a
Movieclass withTitle,Year, and two constructors: one that accepts both values and one that creates an unknown movie from year0. - Write a
Rectangleclass whose constructor rejects negative width or height. Add anArea()method and print the area of two rectangles. - Create a base class
Vehiclewith a constructor that accepts a model name, then deriveCarwith an extraDoorsproperty. Usebase(model)in the derived constructor.
Summary
- A constructor runs when an object is created and prepares the object for use.
- If a class has no constructors, C# supplies a public parameterless constructor.
- Writing any constructor removes that compiler-provided parameterless constructor.
- Constructors can be overloaded, chained with
this(...), and connected to base classes withbase(...). - Good constructors protect object invariants by validating required data early.
