NeuroAgent

C# Constructor Chaining: Ultimate Guide

Master C# constructor chaining: Learn how to use `this()` syntax to call constructors from other constructors, eliminate code duplication, and properly initialize readonly fields in C#.

Question

How to call one constructor from another in C# to avoid code duplication?

I have a C# class with two constructors that initialize readonly fields:

csharp
public class Sample
{
    public Sample(string theIntAsString)
    {
        int i = int.Parse(theIntAsString);
        _intField = i;
    }

    public Sample(int theInt) => _intField = theInt;
    public int IntProperty => _intField;

    private readonly int _intField;
}

One constructor receives values directly, while the other performs calculations before setting the fields. I need to:

  1. Avoid duplicating the field initialization code
  2. Keep fields readonly (they must be set in the constructor)
  3. Call one constructor from another

What’s the proper way to implement constructor chaining in C# to share initialization logic between constructors?

NeuroAgent

Constructor chaining in C# allows you to call one constructor from another using the this() syntax, eliminating code duplication while maintaining readonly field initialization. The key is to have the more complex constructor call the simpler one after performing necessary calculations, ensuring all readonly fields are properly set during object construction.

Contents

Understanding Constructor Chaining

Constructor chaining is the process of calling one constructor from another constructor within the same class or from a base class. As NashTech Blog explains, “Constructor Chaining is the process of calling one constructor from another constructor within the same class or from a base class.”

This technique serves several important purposes:

  • Code reusability: Eliminates duplicate initialization code across constructors
  • Consistency: Ensures all constructors follow the same initialization pattern
  • Maintainability: Changes to initialization logic only need to be made in one place
  • Readonly field management: Properly handles readonly field initialization requirements

The order of execution is crucial: “First, we call the constructor which is referenced with the :this() keyword, and if that also references another constructor, it will also call that constructor, climbing up the call chain” according to Pluralsight.

The this() Syntax

The this() syntax is used to call one constructor from another constructor in the same class. The syntax places the call at the beginning of the constructor definition, followed by a colon and the this() keyword with appropriate parameters.

csharp
public ClassName(parameters)
    : this(otherParameters)
{
    // Constructor body executes after the chained constructor completes
}

Key rules about constructor chaining:

  • The this() call must be the first statement in the constructor
  • Only one constructor can be called (either this() or base())
  • You can chain multiple constructors through a chain of calls
  • The call chain must eventually reach a constructor that doesn’t call another

As Tutorial.TechAltum states, “This can be done by the keyword this and base.”

Implementing Constructor Chaining with Readonly Fields

For your specific example with readonly fields, the proper implementation would be:

csharp
public class Sample
{
    public Sample(string theIntAsString)
        : this(int.Parse(theIntAsString))
    {
        // Additional string-specific initialization if needed
    }

    public Sample(int theInt) => _intField = theInt;
    public int IntProperty => _intField;

    private readonly int _intField;
}

In this implementation:

  1. The string constructor calls the int constructor using : this(int.Parse(theIntAsString))
  2. The int constructor performs the actual field assignment
  3. Both constructors properly initialize the readonly field _intField
  4. No code duplication exists between constructors

The Microsoft Learn documentation confirms that “readonly members can only assigned in the class level or on its constructor,” which is satisfied by this approach.

You could also implement it the other way around, having the more specific constructor call the more general one:

csharp
public class Sample
{
    public Sample(int theInt)
    {
        _intField = theInt;
    }

    public Sample(string theIntAsString)
        : this(int.Parse(theIntAsString))
    {
        // String-specific logic here if needed
    }

    public int IntProperty => _intField;
    private readonly int _intField;
}

Both approaches are valid, but the first one is generally preferred when one constructor is clearly more fundamental than the other.


Advanced Example with Multiple Readonly Fields

For more complex scenarios with multiple readonly fields:

csharp
public class ComplexSample
{
    public ComplexSample(string data)
        : this(ParseData(data))
    {
        // Additional string-specific processing
    }

    public ComplexSample(ParsedData parsedData)
    {
        _primaryField = parsedData.Value;
        _secondaryField = CalculateSecondary(parsedData);
        _timestampField = DateTime.UtcNow;
    }

    // Properties
    public int PrimaryProperty => _primaryField;
    public string SecondaryProperty => _secondaryField;
    public DateTime TimestampProperty => _timestampField;

    private readonly int _primaryField;
    private readonly string _secondaryField;
    private readonly DateTime _timestampField;

    private static ParsedData ParseData(string data)
    {
        // Parsing logic
        return new ParsedData { Value = int.Parse(data) };
    }

    private static string CalculateSecondary(ParsedData data)
    {
        // Calculation logic
        return $"Value: {data.Value}";
    }
}

public class ParsedData
{
    public int Value { get; set; }
}

Best Practices and Patterns

When implementing constructor chaining, consider these best practices:

1. Chain to the most general constructor

  • Have more specific constructors call more general ones
  • This creates a clear hierarchy of initialization logic

2. Handle validation early

  • Perform parameter validation in the constructor that receives the parameters
  • Chain to the next constructor only after validation passes
csharp
public class ValidatedSample
{
    public ValidatedSample(int value)
        : this(value, ValidateValue(value))
    {
    }

    public ValidatedSample(int value, string validationMessage)
    {
        _value = value;
        _validationMessage = validationMessage;
    }

    private static string ValidateValue(int value)
    {
        if (value < 0)
            throw new ArgumentException("Value cannot be negative");
        return "Valid";
    }

    private readonly int _value;
    private readonly string _validationMessage;
}

3. Use private constructors for shared initialization

  • As suggested on Stack Overflow, “you can make a private constructor that can accept both a types of argument, and have your two original constructors both simply chain this one, passing null for the missing argument. This has the advantage over calling private initialisation methods that it plays nicely with readonly fields”

4. Consider parameter object pattern for complex constructors

  • When constructors have many parameters, consider using a parameter object
csharp
public class ParameterizedSample
{
    public ParameterizedSample(string data)
        : this(new SampleParameters(data))
    {
    }

    public ParameterizedSample(SampleParameters parameters)
    {
        _field1 = parameters.Value1;
        _field2 = parameters.Value2;
    }

    private readonly int _field1;
    private readonly string _field2;
}

public class SampleParameters
{
    public SampleParameters(string data)
    {
        Value1 = int.Parse(data);
        Value2 = data.ToUpper();
    }

    public int Value1 { get; }
    public string Value2 { get; }
}

Primary Constructors in Modern C#

C# 12 introduced primary constructors, which provide a more concise syntax for constructor chaining. As Microsoft Learn explains, “Every other constructor for a class must call the primary constructor, directly or indirectly, through a this() constructor invocation.”

Here’s how your example would look with primary constructors:

csharp
public class Sample(int intField)
{
    public Sample(string theIntAsString)
        : this(int.Parse(theIntAsString))
    {
    }

    public int IntProperty => intField;
}

Key benefits of primary constructors:

  • More concise syntax
  • Automatic creation of field/parameter
  • Enforced initialization through chaining
  • Better integration with other language features

However, primary constructors have some limitations:

  • All parameters are publicly accessible by default
  • Parameter validation can be less obvious
  • May not be suitable for all scenarios

Common Use Cases

Constructor chaining is particularly useful in these scenarios:

1. Optional parameters and default values

csharp
public class OptionalParameterSample
{
    public OptionalParameterSample(string name, int age = 18)
    {
        _name = name;
        _age = age;
    }

    public OptionalParameterSample(string name)
        : this(name, 18) // Default age
    {
    }

    private readonly string _name;
    private readonly int _age;
}

2. Factory pattern implementations

csharp
public class FactorySample
{
    private FactorySample() { }

    public static FactorySample CreateFromData(string data)
        => new FactorySample { _data = data };

    public static FactorySample CreateFromFile(string filePath)
        => CreateFromData(File.ReadAllText(filePath));

    private string _data;
}

3. Builder pattern integration

csharp
public class BuilderSample
{
    public BuilderSample(Builder builder)
    {
        _field1 = builder.Field1;
        _field2 = builder.Field2;
    }

    public static Builder CreateBuilder() => new Builder();

    private readonly string _field1;
    private readonly int _field2;

    public class Builder
    {
        public string Field1 { get; set; }
        public int Field2 { get; set; }

        public BuilderSample Build() => new BuilderSample(this);
    }
}

4. Exception handling scenarios

csharp
public class ExceptionHandlingSample
{
    public ExceptionHandlingSample(string data)
        : this(data, ValidateAndParse(data))
    {
    }

    public ExceptionHandlingSample(string data, int parsedValue)
    {
        _data = data;
        _value = parsedValue;
    }

    private static int ValidateAndParse(string data)
    {
        try
        {
            return int.Parse(data);
        }
        catch (FormatException ex)
        {
            throw new ArgumentException("Invalid data format", nameof(data), ex);
        }
    }

    private readonly string _data;
    private readonly int _value;
}

Conclusion

Constructor chaining using the this() syntax is the proper way to share initialization logic between constructors in C# while maintaining readonly field requirements. Key takeaways:

  1. Use : this(parameters) syntax to call one constructor from another, ensuring it’s the first statement
  2. Chain to the most general constructor to create a clear initialization hierarchy
  3. Handle readonly fields properly by assigning them only in constructors
  4. Consider primary constructors in modern C# for more concise syntax
  5. Apply validation and error handling in the appropriate constructor in the chain

By implementing constructor chaining correctly, you eliminate code duplication, improve maintainability, and ensure consistent object initialization across all constructor overloads.

Sources

  1. How to do constructor chaining in C# - Stack Overflow
  2. Call Chain of Constructors in C# | Pluralsight
  3. C# - Call a constructor from another constructor | makolyte
  4. Understanding Constructor Chaining in C# - NashTech Blog
  5. Constructor Chaining in C# - Tutorial.TechAltum
  6. Constructor Chaining : C# 411
  7. Constructor Chaining – 2,000 Things You Should Know About C#
  8. Declare C# primary constructors – classes, structs | Microsoft Learn
  9. readonly keyword - C# reference | Microsoft Learn
  10. C# Constructors with Examples - Tutlane