Back to Blog
C#

C# Primary Constructor Inheritance Explained

c# primary constructor inheritance: Learn how C# primary constructors behave with inheritance: passing parameters to base classes, capturing values, and key limitations.

C# 12Primary ConstructorsInheritanceObject-Oriented ProgrammingConstructor Syntax
Diagram showing a derived class primary constructor passing parameters to a base class primary constructor

When C# 12 introduced primary constructors for classes and structs, inheritance immediately raised a practical question: how does a derived class pass arguments to a base class that also declares a primary constructor? The answer is straightforward, but it involves a few rules that are easy to miss. This article explains the mechanics of c# primary constructor inheritance, shows working examples, and covers the limitations you need to plan around.

Primary Constructor Syntax in Brief

A primary constructor is declared as part of the type declaration. The parameters are in scope throughout the entire class body, but they are not automatically stored as fields unless you explicitly capture them.

public class Product(string name, decimal price) { public string Name { get; } = name; public decimal Price { get; } = price; }

Here, name and price are used to initialize the properties. The compiler generates a constructor that accepts those parameters. If you don't use a parameter anywhere, it is not stored and is only available during construction.

How Inheritance Works with Primary Constructors

When a derived class has a primary constructor, it must call the base class constructor explicitly. The syntax is similar to a traditional constructor initializer, but the base call appears after the parameter list.

public class Book(string title, string author, string isbn) : Product(title, 0m) // base primary constructor call { public string Author { get; } = author; public string Isbn { get; } = isbn; }

The derived class's primary constructor parameters are available in the base initializer, just like they would be in a traditional constructor. This is the core of c# primary constructor inheritance: you can pass any of the the derived class's parameters to the base constructor, as long as as they are in scope.

If you do not provide an explicit base call, the compiler will try to call a parameterless base constructor. If the base class has a primary constructor with required parameters and no parameterless constructor, the code will not compile.

Passing Parameters to a Base Primary Constructor

Consider a base class that validates its own state:

public class Account(string accountNumber, string ownerName) { public string AccountNumber { get; } = accountNumber; public string OwnerName { get; } = ownerName; }

A derived class for a savings account can pass its own parameters through:

public class SavingsAccount(string accountNumber, string ownerName, decimal interestRate) : Account(accountNumber, ownerName) { public decimal InterestRate { get; } = interestRate; }

The base initializer runs before the derived class body. The parameters accountNumber and ownerName are captured by the base class properties, while interestRate is used only in the derived class. The compiler does not create an implicit field for interestRate unless you reference it in an instance member, which the property initializer does.

Using Primary Constructor Parameters in the Derived Class

Primary constructor parameters are available in property initializers, method bodies, and any instance member of the derived class. However, they are not stored as fields unless you capture them. If you need to use a parameter later, you must either assign it to a property or field, or use it in a method that is called later.

public class Order(int orderId, decimal amount) { public int OrderId { get; } = orderId; public decimal Amount { get; } = amount; public string Describe() => $"Order {OrderId} for {Amount:C}"; }

In this example, orderId and amount are captured because they are used in property initializers. If you only used them inside Describe(), the compiler would still generate fields because the method can be called after construction. The compiler decides whether to store a parameter based on whether it is used in a member that can outlive the constructor.

Limitations and Edge Cases

Primary constructors have specific constraints that affect inheritance scenarios.

  • You cannot have both a primary constructor and a traditional constructor with the same signature. The primary constructor is the only constructor that takes the declared parameters.
  • Primary constructor parameters cannot be used in static members or static fields. They are instance-scoped by design.
  • If a derived class has a primary constructor, it cannot also have a traditional constructor that calls a different base constructor. The primary constructor is the sole constructor entry point.
  • You cannot use primary constructor parameters in a base call that is conditional or in a this call. The base initializer is fixed.

A common mistake is trying to use a primary constructor parameter in a static property:

public class Example(int value) { public static int StaticValue => value; // Compiler error }

This fails because value is not available in a static context. The parameter only exists for the instance being constructed.

Another edge case: if a derived class does not need to pass any parameters to the base, but the base has a primary constructor, you must still call it explicitly with some values. There is no implicit call to a primary constructor.

Compatibility and Language Version Requirements

Primary constructors are a C# 12 compiler feature. They do not require runtime support, so they work on any .NET runtime that the compiler can target, including .NET Framework, provided you use a compiler that supports C# 12. In practice, you need Visual Studio ‌17.8 or later, or the .NET 8 SDK, and you must set the<LangVersion> to 12 in your project file if you are not using the latest SDK.

<PropertyGroup> <LangVersion>12</LangVersion> </PropertyGroup>

If you are using a modern .NET SDK, C# 12 is the default for .NET 8 and later. For older target frameworks, the language version is independent of the target framework, so you can use primary constructors as long as the compiler supports them. This makes primary constructors a safe feature to adopt for new code, but you should verify your build environment supports C# 12 before relying on them.

When to Prefer Primary Constructors Over Traditional Constructors

Primary constructors shine when a type's main purpose is to hold data and the constructor simply assigns that data to properties. They reduce boilerplate and make the class signature explicit. Use them when:

  • The class has few dependencies and no complex validation logic in the constructor.
  • The parameters map directly to properties or are used only during construction.
  • You want to keep the type declaration concise, especially for DTOs or value objects.

Avoid primary constructors when:

  • You need multiple constructors with different parameter sets.
  • You need to perform complex validation that involves throwing exceptions from a helper method.
  • You need to use constructor parameters in static members or in a way that requires them to be stored explicitly.
  • You are working with a codebase that targets older language versions and you cannot update the compiler.

For inheritance, primary constructors work well when the derived class also has a straightforward data shape. If the derived class needs to call a different base constructor based on runtime conditions, a traditional constructor gives you more flexibility because you can write multiple constructor overloads.

The decision ultimately comes down to whether the primary constructor's constraint of a single constructor signature is acceptable. For many types, it is. For types that need overloads or conditional base calls, stick with traditional constructors.

c# primary constructor inheritance: Practical Usage and Code | RYUSLOG DEV