c# base constructor: Syntax and Usage
Learn how to call a c# base constructor from a derived class: syntax, parameter passing, when it is required, and common pitfalls.
When you define a class that inherits from another, the derived class must ensure the base class is properly initialized. In C#, the base keyword in a constructor initializer lets you call a specific constructor from the base class. This post covers the c# base constructor syntax, how to pass parameters, when it is mandatory, and common pitfalls you might encounter.
Why a Derived Class Needs to Call a Base Constructor
Every class in C# has at least one constructor. When a derived class is instantiated, the runtime must invoke a constructor on the base class before the derived class's own constructor body executes. If you don't explicitly call a base constructor, the compiler implicitly calls the parameterless constructor of the base class, if one exists. If the base class only has constructors that require parameters, you must explicitly call one using the base keyword.
Consider this example:
public class Vehicle { public string Make { get; } public Vehicle(string make) { Make = make; } } public class Car : Vehicle { public string Model { get; } public Car(string make, string model) : base(make) { Model = model; } }
Here, the Car constructor takes make and model parameters. It passes make to the base Vehicle constructor using base(make). Without that call, the code would not compile because Vehicle has no parameterless constructor.
Syntax and Placement of the base Constructor Initializer
The base constructor initializer appears in the constructor declaration after the parameter list and before the constructor body. It always begins with the colon followed by the keyword base and a parenthesized argument list. The arguments must match one of the base class constructor signatures.
public DerivedClass(args) : base(args) { // constructor body }
If the base class has multiple constructors, you can choose which one to call by supplying the appropriate arguments. For example:
public class Point { public int X { get; } public int Y { get; } public Point() : this(0, 0) { } public Point(int x, int y) { X = x; Y = y; } } public class ColoredPoint : Point { public string Color { get; } public ColoredPoint(int x, int y, string color) : base(x, y) { Color = color; } public ColoredPoint(string color) : base() { Color = color; } }
The first ColoredPoint constructor selects the two-parameter Point constructor. The second selects the parameterless one, which itself chains to the two-parameter one via this. This kind of constructor chaining keeps initialization logic centralized.
Passing Expressions and Static Values to base()
The arguments you pass to base() can be any expressions that evaluate to types compatible with the base constructor's parameters. That includes constants, variables from the current constructor's parameters, static method calls, or even conditional expressions.
public class Temperature { public double Celsius { get; } public Temperature(double celsius) { Celsius = celsius; } } public class BoilingPoint : Temperature { public BoilingPoint() : base(100) { } } public class CustomTemp : Temperature { public CustomTemp(double fahrenheit) : base((fahrenheit - 32) * 5 / 9) { } }
In the CustomTemp example, the constructor converts Fahrenheit to Celsius before passing it to the base constructor. The expression is evaluated before the base constructor executes, which is consistent with the order of initialization.
When the base Call Is Implicit (and When It Is Not)
If the base class has a parameterless constructor, you can omit the base() initializer entirely. The compiler inserts an implicit call to base(). If the base class does not have a parameterless constructor, omitting the call is a compile-time error.
This behavior matters when you refactor a base class. If you remove the parameterless constructor and add a constructor that requires arguments, every derived class that relied on the implicit call must be updated with an explicit base(...).
public class BaseClass { public BaseClass(int value) { } } public class DerivedClass : BaseClass { // Compiler error: 'BaseClass' does not contain a constructor that takes 0 arguments public DerivedClass() { } }
To fix it, you must supply an argument:
public class DerivedClass : BaseClass { public DerivedClass() : base(42) { } }
Order of Execution: Base First, Then Derived
When a derived constructor runs, the base constructor executes before the derived constructor body. This is a critical ordering guarantee. Any fields initialized in the base constructor are ready by the time the derived constructor starts. If you try to access base-class state in the derived constructor body, you can rely on it being set.
Field and property initializers run in a specific order. Base class field initializers run before the base constructor. Derived class field initializers run before the derived constructor's body, immediately after the base constructor returns. Understanding this order helps avoid subtle bugs when you initialize state in multiple layers.
public class Base { protected int Value; public Base() { Value = ComputeInitialValue(); } protected virtual int ComputeInitialValue() { return 10; } } public class Derived : Base { private int extra = 5; public Derived() { } protected override int ComputeInitialValue() { return 100; } }
When you create a Derived instance, the base constructor calls ComputeInitialValue(), which is overridden in Derived. At that point, the derived class's field initializers have not run yet (they run after the base constructor). So the override may observe default values for derived fields, leading to unexpected behavior. This is a known pitfall when calling virtual methods from constructors.
Common Mistakes and How to Avoid Them
One frequent mistake is trying to call base() from a constructor body rather than in the initializer. The base keyword is only valid in the initializer position, not inside braces. Attempting to do so results in a syntax error.
Another mistake is passing arguments in the wrong order or with the wrong types. The compiler resolves the base constructor based on the argument list, so mismatched types cause resolution failure.
A more subtle issue occurs when the base constructor uses virtual dispatch. As shown above, if the base constructor calls a virtual method, the derived override runs before the derived class has fully initialized. This can produce null references or default values. To avoid this, do not call virtual methods in constructors, or document that behavior clearly.
Constructor Chaining and Maintainability
Constructor chaining, combining this(...) and base(...), helps reduce duplicate initialization code. It keeps the logic for setting up an object in one place, and derived constructors simply pass the relevant parameters upward. This makes the class hierarchy easier to maintain because central changes to base initialization propagate to all derived types.
However, deep inheritance chains with heavy constructor logic can become hard to follow. Each level of chaining adds indirection. If you find that a derived class needs many unrelated parameters just to satisfy its base class, consider whether composition might be a better design than inheritance.
Compatibility Considerations When Introducing or Removing Constructors
When you add a new constructor to a base class, derived classes are unaffected if they do not rely on that specific signature. But when you remove or change a constructor that derived classes use, you break compilation. This is especially important in libraries where derived classes exist outside your control. To maintain compatibility, keep at least one constructor that derived classes can call, or provide a protected parameterless constructor that supplies defaults.
The base keyword also works in structs? No, structs do not support inheritance, so the base constructor initializer only applies to classes. Records, however, follow similar rules. A record can inherit from another record, and its primary constructor must call a base constructor using the same base(...) syntax.
public record Person(string Name, int Age); public record Employee(string Name, int Age, string Department) : Person(Name, Age);
This record inheritance demonstrates the same initialization principle in a more concise syntax.
When Not to Use a Base Constructor Call Explicitly
If the base class has a parameterless constructor and you do not need to pass any values, you can omit the base() call entirely. Adding it explicitly does not change behavior, but it can document intent. Some developers prefer to write : base() to make the dependency visible, especially if the parameterless constructor has side effects. Either style is correct; choose the one that makes your code clearer to maintainers.
If the base constructor performs expensive work that you want to avoid for a particular derived type, you cannot skip it entirely. The base class must be initialized. You can, however, design a protected constructor on the base class that performs minimal initialization, and have the derived class call that one. This is a tradeoff between correctness and efficiency that you control via the constructors you expose.
Final Word on Base Constructor Invocation
Using base() is a straightforward mechanism, but it interacts with the entire initialization sequence of an object. Understanding when the call is implicit, how parameter passing works, and the order of execution helps you write correct inheritance hierarchies. Keep constructor chains shallow, avoid virtual calls in constructors, and be deliberate about which base constructor your derived class calls.