How to use primary constructors in C#
By FoxLearn 1/2/2025 9:35:06 AM 76
C# 12 introduces support for primary constructors, a feature that is not new to programming. Languages like Scala, Kotlin, and OCaml already allow constructor parameters to be integrated directly in the class declaration.
What are primary constructors?
Primary constructors in C# allow you to declare constructors with parameters directly in a class or struct, making those parameters accessible throughout the type. This eliminates the need to explicitly declare private fields and write code to assign constructor values to them, simplifying the class or struct definition.
public readonly struct Point3D(double x, double y, double z) { public double X { get; } = x; public double Y { get; } = y; public double Z { get; } = z; // You can also define additional methods or properties here if needed }
In this example, Point3D
is a struct that takes three parameters (x
, y
, and z
) in its primary constructor.
public class Circle(double radius) { public double Radius { get; } = radius; public double GetArea() => Math.PI * Radius * Radius; }
In this example, the Circle
class has a primary constructor that takes a radius
parameter. The radius
is automatically used to initialize the Radius
property.
In C#, primary constructor parameters are scoped to the entire class, making them accessible to all instance members, except for secondary constructors. The compiler automatically infers the private fields needed and assigns values to them, eliminating the need for explicit field declarations and assignments.
public class Customer(int id, string firstName, string lastName) { public int Id { get; } = id; public string FirstName { get; } = firstName; public string LastName { get; } = lastName; public string GetFullName() => $"{FirstName} {LastName}"; }
In this example, the Customer
class has a primary constructor that takes id
, firstName
, and lastName
parameters. These parameters are automatically assigned to read-only properties.
The properties of the class are automatically initialized using the parameters defined in the primary constructor, eliminating the need for explicit assignments.
Pros of Primary Constructors in C#:
- Simplified Syntax: Primary constructors reduce code duplication by allowing properties to be declared and initialized directly within the constructor parameters.
- Easy Initialization: They make it easy to initialize properties with default or constructor-provided values.
- Immutability: The
init
modifier can be used with properties to make them read-only after initialization.
Cons of Primary Constructors in C#:
- Reduced Flexibility: They may not be suitable for complex initialization tasks, such as validation or logging, due to their simplicity.
- Less Explicit Code: While reducing boilerplate, they can make the code harder to understand for less experienced developers.
- Compatibility Issues: Integrating primary constructors into an existing codebase could be difficult, especially in large or complex projects.
- Limited Control Over Access Modifiers: Primary constructors provide less control over access modifiers compared to traditional constructors.
- Learning Curve: As a new feature, primary constructors may require time for developers to learn, potentially slowing down development.
Using overloaded constructors with a primary constructor in C#
You can use overloaded constructors in any class that has a primary constructor, allowing for different ways to instantiate the class with varying parameter sets.
public class User(int id, string userName, string password) { public int Id { get; } = id; public string UserName { get; } = userName; public string Password { get; } = password; // Primary constructor for basic user creation public User(int id, string userName, string password) { Id = id; UserName = userName; Password = password; } // Overloaded constructor with default password public User(int id, string userName) : this(id, userName, "defaultPassword123") // Default password set { } // Overloaded constructor for anonymous user public User(int id) : this(id, "Anonymous", "defaultPassword123") // Default values for anonymous user { } public string GetDetails() => $"User {UserName} with ID {Id}"; }
Using primary constructors in record types vs classes in C#
Here's example that demonstrates the difference between record types and class types with primary constructors, focusing on the behavior of immutability and mutability:
Record Type with Primary Constructor
public record ProductRecord(int Id, string Name, decimal Price);
You can create an instance of the ProductRecord
record type like this:
var productRecord = new ProductRecord(1, "Laptop", 999.99);
Once the productRecord
instance is created, you cannot modify its properties. Attempting to change a property will result in a compiler error:
// This will cause a compile-time error: productRecord.Price = 899.99;
In records, if you want to "modify" a property, you can use the with
expression to create a new instance with updated values:
// Correct way to modify a record (creating a new instance): var updatedProductRecord = productRecord with { Price = 899.99 };
Class Type with Primary Constructor
public class ProductClass(int Id, string Name, decimal Price) { // Additional methods or logic can go here }
You can create an instance of the ProductClass
class type in the same way as the record:
var productClass = new ProductClass(1, "Laptop", 999.99);
The properties of a class can be modified after the instance is created. You can directly assign new values to the properties of productClass
:
// This works without issue: productClass.Price = 899.99;
Use dependency injection in primary constructors in C#
Primary constructors in C# simplify dependency injection by allowing you to inject dependencies directly into a class's constructor, reducing boilerplate code.
Here's example using a primary constructor for dependency injection in a C# class:
public class ProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public async Task<IEnumerable<Product>> GetProducts() { return await _productRepository.GetAll(); } }
The ProductService
class has a constructor that accepts an IProductRepository
and assigns it to a private field (_productRepository
).
With Primary Constructor (C# 12)
public class ProductService(IProductRepository productRepository) { public async Task<IEnumerable<Product>> GetProducts() { return await productRepository.GetAll(); } }
With C# 12, you can inject IProductRepository
directly through the primary constructor, simplifying the code. There's no need to explicitly declare a private field or assign the parameter manually.
By using primary constructors, you eliminate the need for explicit constructor definitions, reducing boilerplate code and improving readability.
- How to fix 'Failure sending mail' in C#
- How to Parse a Comma-Separated String from App.config in C#
- How to convert a dictionary to a list in C#
- How to retrieve the Executable Path in C#
- How to validate an IP address in C#
- How to retrieve the Downloads Directory Path in C#
- C# Tutorial
- Dictionary with multiple values per key in C#