AlgoMaster Logo

Constructor Chaining

Last Updated: May 17, 2026

11 min read

A derived class inherits fields and methods from its base, but it doesn't inherit constructors. When you create a derived object, both the base part and the derived part of that object need to be initialized, and the language has strict rules about the order in which that happens. This chapter is about those rules: how constructors call each other across a hierarchy with base(...), how they call each other inside the same class with this(...), what order fields and constructor bodies actually run in, and the surprising bug you can hit if you call a virtual method too early.

Why Chaining Exists

A derived object is one object in memory, but it has two parts: the slice inherited from the base class, and the new fields the derived class adds on top. Both parts need to be set to sensible values before any code runs that depends on them.

The rule the runtime enforces is simple: the base part must be fully initialized before the derived part. That's why the base constructor runs first. If the derived constructor were allowed to run first, it could read or call into fields and methods that the base hadn't set up yet, and you'd be working with half-built state.

Take an E-Commerce example. A Customer has a name and an email. A PremiumCustomer inherits those and adds a discount rate. When you create a PremiumCustomer, the runtime has to set up Name and Email first (the Customer part), then set up DiscountRate (the PremiumCustomer part). If those happened in the wrong order, the premium constructor could try to greet the customer by name before the name was even assigned.

The base(name, email) call is how PremiumCustomer tells the compiler "use this Customer constructor for the base part." It runs first, sets Name and Email, then control returns to the PremiumCustomer body, which sets DiscountRate. By the time the derived constructor's body executes, the base part is already in place.

The Execution Order: Top-Down

A hierarchy with three levels makes the order clearer. Imagine an Account base class (every E-Commerce user has one), a Customer that extends it with shopping-specific info, and a PremiumCustomer that adds discount logic on top.

The constructors run top-down: Account first, then Customer, then PremiumCustomer. Each one finishes its body completely before the next one starts. By the time PremiumCustomer's body runs, Id and Name are already set, so it can safely use them.

Here is the same picture as a diagram:

The flow goes down the chain to find the deepest base constructor, then unwinds upward, running each body in order. It's worth reading this diagram in two passes: first follow the orange "invocation" path going down, then follow the green "body executes" path going back up.

What Happens Without an Explicit base(...)

If you don't write : base(...) on a derived constructor, the compiler inserts an implicit : base() call to the base class's parameterless constructor. As long as the base class has one, this works fine.

PremiumCustomer's declaration has no : base(...), so the compiler treats it as if you had written : base(). The base parameterless constructor runs first, then the derived one.

Now look at the version where the base class has only a parameterized constructor:

The compiler tries to insert : base(), but Customer doesn't have a parameterless constructor. The result is a compile error like CS7036: There is no argument given that corresponds to the required formal parameter 'name' of 'Customer.Customer(string)'.

The fix is to write the call explicitly:

Once : base(name) is in place, the compiler is happy. The lesson is that whenever the base class doesn't have a usable parameterless constructor, every derived constructor must explicitly route to one that the base does have.

Calling a Specific Base Constructor

A base class can declare more than one constructor. The derived class picks which one to call by writing the arguments in : base(...).

Each PremiumCustomer constructor picks exactly one base constructor by matching argument types. Overload resolution at the base(...) call works the same way as overload resolution for ordinary method calls: the compiler picks the best match based on the argument types you supply.

You can also pass computed values to base(...), not just parameters. The arguments are ordinary expressions:

Two things to know about what's legal inside the base(...) argument list:

  • You can use the constructor's parameters, method calls, and static members.
  • You cannot use this to read instance state, because the object isn't initialized yet. The compiler blocks it.

this(...): Chaining Inside the Same Class

base(...) chains to the parent class. this(...) chains to another constructor in the same class. It's how you avoid repeating initialization logic across multiple constructors of one class.

A class might offer several ways to create itself. Without this(...), every constructor has to repeat the same assignments:

With this(...), the simpler constructors delegate to the most-detailed one:

The two shorter constructors have empty bodies. Their entire job is to call the three-parameter constructor with sensible defaults. Now the assignment logic exists in exactly one place.

A constructor can chain to this(...) or base(...), but not both at once. If you write : this(...), the compiler knows the other constructor in this class is responsible for calling base(...). The chain eventually ends at a constructor that uses : base(...) (or relies on the implicit one), and that's where the base part of the object gets initialized.

Combined Chains: this(...) Then base(...)

A class can mix both styles. The shorter constructors chain to a "main" constructor with this(...), and the main constructor chains to the base with base(...). The final result is one well-defined initialization path.

Read the order carefully. The caller invokes PremiumCustomer(string, decimal). Its : this(name, discountRate, 0) redirects to the three-parameter constructor before any body runs. That three-parameter constructor has : base(name), so the Customer constructor runs first. Then the three-parameter body runs, then control returns to the two-parameter body, then the new expression completes.

The pattern in one line: chains resolve downward (to the most specific base) before any body runs, and bodies execute from base up to the originally-called constructor.

Field Initialization Order

There's a third thing besides base constructor calls and constructor bodies: field initializers. When you write private int _count = 0; or public string Tag { get; } = "default"; on a field or property, that initializer runs as part of construction. The order matters and trips people up.

For a single class, the order is:

  1. Derived class field initializers run first.
  2. The base constructor chain runs (which itself triggers base field initializers, then the base body).
  3. Derived constructor body runs.

That's not a typo: derived field initializers run before the base constructor body. Here is a worked example:

The output reveals the actual order:

  1. PremiumCustomer field initializers run first (the derived class).
  2. Control then moves into the base chain: Customer field initializers run.
  3. Customer body runs.
  4. PremiumCustomer body runs.

The reason for this order is subtle. Field initializers in C# are guaranteed to run before any constructor body in the same class can see them. The compiler effectively inlines them at the start of every constructor, before the base(...) call. That's why the derived class's initializers run before the base constructor body. It would be wrong for them to run later, because then a virtual method call from the base body might observe uninitialized derived fields (and the next section shows just how bad that gets).

The Dangerous Pattern: Virtual Calls From a Base Constructor

The execution order has a real consequence. When the base constructor body runs, the derived constructor body has not run yet. The derived field initializers have, but anything in the derived constructor body, validation, computed properties, defensive defaults, hasn't happened.

If the base constructor calls a virtual method, and the derived class overrides that method, the override runs against a half-built object. The override sees the derived fields' initializer values, but anything set up in the derived body is still at its default (null, 0, false).

Notice the empty space where Gold should be. _tier was still null when PrintGreeting() ran, because the Customer constructor invoked the virtual method before the PremiumCustomer body got a chance to assign _tier. The override ran (because it's virtual), but the field it depends on wasn't ready.

In nullable-reference-types-enabled code, Console.WriteLine($"Hello from {_tier} customer") with _tier == null would also surface a warning at the field declaration that _tier is uninitialized when leaving the constructor. The runtime behavior is the same: the override interpolates a null value.

Here is the same flow as a sequence:

The fix is one of a few options:

  • Don't call virtual methods from constructors. Initialize state in the constructor, and let callers invoke the virtual method after the object is fully constructed.
  • Make the called method non-virtual or sealed, so it can't be overridden in a way that depends on uninitialized state.
  • Use an init method called after construction, when the object is in a valid state.

The simplest fix in the example is to drop the virtual call from the constructor:

Now the greeting happens after construction is complete, with _tier properly set. The behavior is correct, and the rule to remember is: don't call virtual methods from a constructor. If your design seems to need it, the design is probably asking for a redesign.

Default Constructors and Inheritance

When a class declares no constructor at all, the C# compiler generates a default parameterless constructor for it. That synthesized constructor has an implicit : base() and an empty body. It's how you can write a class with no constructors and still say new MyClass().

As soon as you declare any constructor (even a parameterless one), the compiler stops generating the default. From that point on, the constructors you write are the only ones available.

This ripples into inheritance. If a base class has only a parameterized constructor, derived classes can't rely on the implicit : base() call, as covered earlier. They must use explicit : base(...).

A useful summary table:

Base class declares...Derived class needs to...
Nothing (compiler-generated parameterless)Nothing extra. Implicit : base() works.
An explicit parameterless constructorNothing extra. Implicit : base() works.
Only parameterized constructorsWrite explicit : base(args) on every derived constructor.
Both parameterless and parameterizedChoose: implicit : base() picks the parameterless one; explicit : base(args) picks a parameterized overload.

A small detail about derived classes: if a derived class declares no constructors and the base has a parameterless constructor, the derived class also gets a default parameterless constructor that chains to it.

If the base class has only parameterized constructors and the derived class declares no constructors, the derived class still gets a compiler-generated parameterless constructor, but the compilation fails because that synthesized : base() has no matching base constructor. You have to write a real constructor in the derived class with an explicit : base(...).

A short note on records: record types also support constructor chaining with : base(...) and : this(...). The primary constructor syntax public record PremiumCustomer(string Name, decimal Rate) : Customer(Name) is a compact way to write a derived constructor that chains to a base record. The rules described here still apply.

Putting It Together

A combined example that uses every concept from this chapter: a three-level hierarchy, this(...) chaining inside a class, explicit base(...), field initializers, and a fully-constructed virtual call done after construction.

A few things to notice. The PremiumCustomer constructor uses : base(id, name, email). That picks Customer's three-parameter constructor, which uses : base(id) to chain into Account. Account's body runs first, then Customer's body, then PremiumCustomer's body. The two-parameter Customer constructor isn't used at all in this run; it exists for callers who want to skip the email.

Then, after construction is complete, the program calls DescribeTier(). The override runs against a fully-built object, so DiscountRate is already set and the result is correct. This is the safe pattern: build the object, then call virtual methods on it.

When to Reach for Each Tool

A quick reference for which mechanism to use when:

You want to...Use
Run a specific base-class constructor: base(args)
Forward to a more detailed constructor in the same class: this(args)
Give consumers multiple ways to construct the typeA "main" constructor others chain to with : this(...)
Keep base initialization separate from derived state: base(...) in the most-detailed constructor; chain the rest with : this(...)
Avoid the half-built object bugDon't call virtual methods from constructors
Force callers to supply a value the base needsDrop the base's parameterless constructor; require : base(value) on every derived constructor

The bigger picture is that constructors compose. A derived constructor isn't a self-contained piece of code, it's a step in a chain that also touches the base class and possibly other constructors of the same class. Treat the chain as a unit. When you change one constructor, scan the rest to make sure they still make sense together.

Summary

  • A derived object is initialized base-first, derived-last. The base constructor runs before the derived constructor body so derived code can safely depend on base state.
  • Use : base(args) on a derived constructor to pick which base-class constructor runs. Without it, the compiler inserts : base(), which works only when the base class has a parameterless constructor.
  • Use : this(args) to chain to another constructor in the same class, avoiding duplicated initialization logic. The chain eventually reaches a constructor that triggers : base(...).
  • Field initializers run from most-derived to base, before the base constructor body. Constructor bodies run from base to most-derived. The two halves form a V.
  • A constructor can use : this(...) or : base(...), never both at the same time.
  • The compiler generates a default parameterless constructor only when a class declares no constructors at all. Once you write any constructor, the default is gone.
  • Calling a virtual method from a base constructor is a silent correctness bug. The override runs against a half-built derived object because the derived constructor body hasn't executed yet. Initialize state in constructors and invoke virtual methods afterward.
  • When changing one constructor in a class, scan the others. They share state and chain to each other, so a change in one often needs to be reflected across the set.

Constructor chains let derived classes build on base initialization. But sometimes you want to STOP inheritance from going any deeper. That's what sealed is for. The _Sealed Classes & Methods_ lesson covers how to lock down a type so no one can extend it further, why you might want to, and what the runtime and compiler do differently when they know a class or method can't be overridden.