C# Constructor Chaining Explained
Learn how to reuse constructor logic with c# constructor chaining using `this` and `base`, including syntax, common pitfalls, and design considerations.
When a class has multiple constructors, each one often repeats the same initialization steps: setting default values, validating arguments, or assigning dependencies. C# constructor chaining lets one constructor call another on the same object using the this keyword, while base delegates to a base class constructor. This keeps initialization logic in one place and prevents the same checks from being duplicated across constructor overloads.
How this and base Work in Constructor Chaining
Constructor chaining in C# uses two keywords: this and base. The this keyword invokes another constructor from the same class, while base invokes a constructor from the immediate base class. Both must appear in the initializer list, immediately after the constructor signature, as shown below.
public class Order { public int Id { get; } public Customer Customer { get; } public decimal Total { get; } public Order(int id, Customer customer) : this(id, customer, 0m) { } public Order(int id, Customer customer, decimal total) { Id = id; Customer = customer; Total = total; } }
The first constructor delegates to the second, which is the only place where the properties are assigned. As a result, Id, Customer, and Total are set in exactly one location, so any future change, such as adding validation for negative totals, is made once rather than in every overload.
The chained constructor runs before the body of the calling constructor. If the calling constructor requires side effects after the chain, such as logging or additional setup, those statements go in its own body and run after the chained constructor returns.
Common Patterns: Default Values, Optional Parameters, and Required Initialization
A frequent use is providing a parameterless or minimal constructor that supplies defaults to a full constructor.
public class LogMessage { public string Message { get; } public LogLevel Level { get; } public DateTime Timestamp { get; } public LogMessage(string message) : this(message, LogLevel.Information) { } public LogMessage(string message, LogLevel level) : this(message, level, DateTime.UtcNow) { } public LogMessage(string message, LogLevel level, DateTime timestamp) { Message = message ?? throw new ArgumentNullException(nameof(message)); Level = level; Timestamp = timestamp; } }
Here, the shortest constructor chains to the next, which chains to the third. The final constructor performs validation and assignment. Callers can supply only a message, a message with a level, or all three arguments, but the validation that message is not null runs only once in the last constructor.
This pattern is effective when overloads are known at compile time. If the number of combinations grows, consider optional parameters instead.
public LogMessage(string message, LogLevel level = LogLevel.Information, DateTime? timestamp = null) { Message = message ?? throw new ArgumentNullException(nameof(message)); Level = level; Timestamp = timestamp ?? DateTime.UtcNow; }
The optional-parameter version reduces code but changes the public API: callers can now omit arguments in any order that respects the signature, and the compiler resolves defaults. The choice between optional parameters and constructor chaining depends on whether you need distinct constructor bodies or whether a single body with defaults is sufficient.
Realistic Example: Reusing Validation and Resource Setup
A data repository often needs an open connection or a configured client before its methods can run. Constructor chaining can centralize that setup.
public class UserRepository { private readonly SqlConnection _connection; private readonly int _timeoutSeconds; public UserRepository(string connectionString) : this(new SqlConnection(connectionString), 30) { } public UserRepository(SqlConnection connection) : this(connection, 30) { } public UserRepository(SqlConnection connection, int timeoutSeconds) { _connection = connection ?? throw new ArgumentNullException(nameof(connection)); if (timeoutSeconds <= 0) throw new ArgumentOutOfRangeException(nameof(timeoutSeconds)); _timeoutSeconds = timeoutSeconds; } }
The first two constructors accept different input types but both end up at the full constructor, which checks for null and validates the timeout. Without chaining, each constructor would need its own null check, and a change in validation policy would require editing multiple places.
One limitation is that the chained constructor cannot use the returned object of another constructor, nor can it assign fields before the chain completes. Therefore, if a constructor needs to transform input before validation, run that transformation in its own body after the chain, or perform it outside the constructor entirely.
Constructor Chaining and Inheritance: Working with base
When a class derives from another, constructor chaining also applies upward. A derived constructor can call a base constructor with base.
public class BaseEntity { public Guid Id { get; } public DateTime CreatedAt { get; } public BaseEntity(Guid id) { Id = id; CreatedAt = DateTime.UtcNow; } } public class Order : BaseEntity { public string OrderNumber { get; } public Order(Guid id, string orderNumber) : base(id) { OrderNumber = orderNumber; } }
The base call ensures the base class is fully initialized before the derived class sets its own fields. This is essential because base fields may be readonly and can only be assigned in the base constructor. If the base class lacks a parameterless constructor, the derived constructor must explicitly call base with matching arguments; otherwise, the code will not compile.
When both this and base are involved, the order is: the this chain resolves first, and eventually a constructor calls base before its own body. The important rule is that a constructor cannot chain to this and base in the same initializer—only one is allowed.
Common Mistakes and Compile-Time Errors
A typical error is trying to chain to a constructor that does not exist with the given argument types. The compiler reports a CS1729 error: 'Type' does not contain a constructor that takes 'n' arguments. Verify the constructor signature matches the types you are passing.
Another mistake is updating a utility constructor but forgetting to update a chaining caller that relies on its behavior. For example, if the full constructor now enforces an upper bound on the total, an overload that passes a literal value may silently fail at runtime. Keep the chaining contract explicit so overloads only add arguments, not change their meaning.
A less obvious issue occurs when a chained constructor throws an exception. The exception propagates out of the calling constructor, so any object being created is never fully constructed. Ensure that chained constructors do not assume that later overloads have run already; the chain runs in order from least specific to most specific, but the calling body runs last.
Performance, Memory, and Maintainability Considerations
Constructor chaining itself has negligible performance cost. The generated IL performs the same assignment operations as a single constructor that contains all statements. The real cost is architectural: if the chain is long and each level performs significant work, that work happens for every object creation. For short chains and simple assignments, the overhead is not measurable.
Memory usage is unaffected because chaining does not allocate extra objects, collections, or buffers. The runtime calls a constructor method just as it would with any other method. Therefore, constructor chaining is not a performance concern unless the constructor body performs heavy operations, such as reading a file or calling a web service, which should never be part of a constructor anyway.
From a maintainability perspective, chaining reduces duplication, which is a clear benefit. However, it can obscure the flow of initialization if the chain grows beyond three or four levels. At that point, consider splitting the class or using a factory method that takes a single options object.
When Not to Use Constructor Chaining
Chaining is not always the right tool. If a class has constructors that set completely different fields with no shared logic, chaining forces a common signature and may introduce unnecessary coupling. For example, a constructor that only sets Id and another that only sets Name do not share meaningful logic, so forcing them to chain might require a third constructor that accepts both and uses null defaults.
Similarly, if the construction logic depends on the type of argument, such as different behavior for a string versus a Stream, an overload with chaining may not fit because the decision must be made inside the body. In those cases, use separate constructor bodies or static factory methods.
The rule of thumb: chain when constructors share the same core initialization; stop chaining when the overloads diverge in behavior or when the chain becomes a source of confusion.
Advanced Pattern: Chaining with Static Factory Methods for Validation
Constructor chaining can combine with static factory methods to enforce rules that are not representable with the constructor signature. For instance, you can expose a Create method that performs validation and then invokes a private constructor.
public class Temperature { public double Celsius { get; } private Temperature(double celsius) { Celsius = celsius; } public static Temperature FromCelsius(double value) { if (double.IsNaN(value) || double.IsInfinity(value)) throw new ArgumentOutOfRangeException(nameof(value)); return new Temperature(value); } public static Temperature FromFahrenheit(double value) { var celsius = (value - 32) * 5 / 9; return FromCelsius(celsius); } }
Here, the private constructor cannot be called directly outside the class, so all creation goes through static methods. The FromFahrenheit method converts and then calls FromCelsius, which is not constructor chaining itself but uses a similar principle: centralizing the validation in one method. The constructor is protected because it trusts that the validation already happened.
This pattern works well when construction is complex or when you want to hide constructor overloads from callers. It keeps the chain visible and testable without exposing a confusing public surface.
Constructor Chaining with Record Types
C# 9 introduced records, which provide a primary constructor and positional parameters. Records still support constructor chaining, but the syntax is slightly different.
public record Person { public string FirstName { get; init; } public string LastName { get; init; } public Person() : this("Unknown", "Unknown") { } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } }
The parameterless constructor chains to the two-argument constructor, which is the primary one. This works with records as with classes. However, records also support primary constructors, where the parameters are available throughout the record body. In that case, chaining may be unnecessary because the compiler generates the primary constructor automatically. Use chaining only when you need extra overloads that derive from the primary constructor.
Constructor Chaining in Dependency Injection Scenarios
Dependency injection containers typically require a single public constructor, but chaining can still appear when you manually instantiate objects. For example, a service that accepts an optional logger can chain to a default logger instance.
public class OrderService { private readonly ILogger _logger; private readonly IOrderRepository _repository; public OrderService(IOrderRepository repository) : this(repository, new NullLogger()) { } public OrderService(IOrderRepository repository, ILogger logger) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } }
The two-constructor version is still valid for DI, but if you register the type with a DI container, the container will pick the constructor with the most parameters it can resolve, which is usually the two-argument one. The chaining constructor is useful for unit tests or small applications that don't use a container.
Avoid heavy work in chained constructors that might be called by the DI container, because the container may invoke the longest constructor and skip the shorter one. That means validation or setup placed only in the short constructor will not run in DI scenarios. The solution is to keep all required setup in the full constructor and treat the short ones as convenience overloads.
Constructor Chaining and Object Initializers: Complementary Tools
C# object initializers with init properties can reduce the need for multiple constructor overloads. When a class has many optional properties, an object initializer is often clearer than a long chain.
var message = new LogMessage("Processing started") { Level = LogLevel.Debug, Timestamp = DateTime.UtcNow };
However, object initializers cannot be conditionally applied inside the constructor chain, and they do not work with readonly fields that are set only in a constructor. For mandatory fields, a constructor is required. Constructor chaining remains useful when you need to guarantee that certain fields are set before an object is used. Object initializers are a complement, not a replacement.
A rule of thumb: use constructor chaining for mandatory initialization and invariant enforcement; use object initializers for optional configuration that does not affect correctness.
Design Tradeoffs: Chain Depth and Readability
Each chained constructor adds a level of indirection. A chain of two or three is easy to follow. A chain of five becomes difficult to trace because the reader must jump between several constructor signatures to understand what values are actually initialized. If the chain grows too deep, refactor by introducing a single constructor that accepts a parameter object, such as an OrderInitializationOptions class, or by using a builder pattern.
The builder pattern allows step-by-step configuration and is a good alternative when you have many optional parameters and validation depends on combinations. Constructor chaining is simpler for a small, fixed set of overloads. There is no universal best, but the threshold for complexity is the point where the chain's logic becomes hard to test independently.
Every chained constructor is a method call, so unit testing can target each constructor individually. This is an advantage because you can verify that each overload routes to the correct defaults without repeating the same test logic for every combination.
The final consideration is compatibility. Constructor chaining works in all C# versions that support constructors, and there are no runtime differences between calling a chained constructor and calling a normal one. Therefore, you can adopt this pattern without worrying about target framework constraints. The only version-dependent aspect is the record syntax, which requires C# 9 or later. For older frameworks, continue using classic classes.
In summary, c# constructor chaining is a practical tool for reducing duplication and centralizing validation. Use this to chain within a class and base to chain to a base constructor. Keep the chain short, ensure each overload is consistent with the primary constructor's intent, and avoid placing heavy operations in constructor bodies. When the chain becomes unwieldy, switch to object initializers, static factories, or builders.