Primary Constructors in C#
Primary Constructors in C#
Primary constructors provide a compact way to declare the parameters a type needs when it is created. They were introduced for classes and structs in C# 12, building on the positional-constructor syntax that records have supported for longer.
The basic idea
Without a primary constructor, the same type requires an explicit constructor:
public class Person
{
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string Name { get; }
public int Age { get; }
}
With a primary constructor, the parameters appear after the type name:
public class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
The parameters name and age are in scope throughout the type body. Assigning them to properties, as above, gives the object stored state. A primary-constructor parameter is not automatically a property or field.
Using parameters in methods
Without a primary constructor, a value used by a property must usually be stored in a field or property:
public class Greeter
{
private readonly string _name;
public Greeter(string name)
{
_name = name;
}
public string Message => $"Hello, {_name}!";
}
With a primary constructor, the parameter can be used directly by members:
public class Greeter(string name)
{
public string Message => $"Hello, {name}!";
}
If a method or member uses a parameter after construction, the compiler creates storage for that captured value when necessary. It is usually clearer to expose long-lived data through an explicitly named property or field, especially when the value represents part of the object’s public state.
Base classes and dependency injection
Without a primary constructor, passing a dependency to a base class takes an explicit constructor:
public class ReportService : Service
{
private readonly IClock _clock;
public ReportService(IClock clock) : base(clock)
{
_clock = clock;
}
public DateTime CreatedAt => _clock.UtcNow;
}
With a primary constructor, the dependency and base-class call are visible in the type declaration:
public class ReportService(IClock clock) : Service(clock)
{
public DateTime CreatedAt => clock.UtcNow;
}
They also work well with dependency-injection scenarios because the required dependencies are visible in the type declaration.
Validation and additional constructors
Without a primary constructor, validation can be performed in the traditional constructor:
public class Account
{
private readonly string _id;
public Account(string id)
{
_id = string.IsNullOrWhiteSpace(id)
? throw new ArgumentException("An account ID is required.", nameof(id))
: id;
}
public string Id => _id;
}
With a primary constructor, the validation can move into a field initializer:
public class Account(string id)
{
private readonly string _id =
string.IsNullOrWhiteSpace(id)
? throw new ArgumentException("An account ID is required.", nameof(id))
: id;
public string Id => _id;
}
A type with a primary constructor can also declare additional constructors. Those constructors must eventually delegate to the primary constructor, directly or indirectly.
When should you use one?
Primary constructors are a good fit when a type has a small set of required inputs and straightforward initialization. Use a traditional constructor when construction involves many steps, overloads, complex validation, or behavior that would be harder to understand in a compact declaration.
The main benefit is not merely fewer lines of code: a primary constructor makes a type’s required inputs visible at the point where the type is declared.