Primary constructors in C# 12 can be used in classes and structs as well as record types. Here’s how they make your code cleaner and more concise.

building construction site / contractor / engineer / hard hat / crane

One of the striking new features in C# 12 is the support for primary constructors. The concept of primary constructors is not new. Several programming languages including Scala, Kotlin, and OCaml provide support for integrating constructor parameters directly at the place where you declare your class.

In this article, we’ll take a close look at primary constructors and how we can work with them in C# 12. To run the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.

Create a console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2022 is installed in your system, follow the steps outlined below to create a new .NET Core console application.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Click Next.
  7. In the “Additional information” window shown next, choose “.NET 8.0 (Long Term Support)” as the framework version you want to use.
  8. Click Create.

We’ll use this .NET 8 console application project to work with primary constructors in the subsequent sections of this article.

What are primary constructors?

Primary constructors enable you to declare constructors in a class or a struct with parameters available throughout the body of the type. Using primary constructors, you can create constructors for your class or struct without writing code—i.e. without explicitly declaring private data members and implementing constructors to assign the values of the constructor parameters to those data members.

The following code snippet illustrates a primary constructor declared inside a struct.

public readonly struct Rectangle(double x, double y) {     //Members of the struct Rectangle }

Below is a simple implementation of a primary constructor declared inside a class.

public class Rectangle(double x, double y) {     //Members of the class Rectangle }

The primary constructor parameters are scoped to the class as a whole. They are accessible to all instance members except secondary constructors. In C#, the compiler infers which private fields we require and captures the corresponding values.

Consider the following piece of code that implements a primary constructor for a class named Customer.

public class Customer(int id, string FirstName, string LastName) {     //Implement the class members here }

The properties pertaining to this class will be initialized automatically using the parameters of the primary constructor.

Pros and cons of primary constructors in C#

Primary constructors in C# offer three key benefits.

  1. Simplified syntax: By using primary constructors, you can declare and initialize properties directly within the constructor parameters, reducing code duplication.
  2. Easy initialization: You can take advantage of primary constructors to initialize properties of a class with default values or specific values passed as constructor parameters.
  3. Immutability: You can leverage the init modifier on properties initialized in the primary constructor to ensure that they are read-only after object initialization.

Primary constructors in C# have a number of downsides as well.

  1. Reduced flexibility: Primary constructors are designed to be simple, which can limit their flexibility. They may not be suitable when complex initialization logic is needed or when additional actions are required, such as validation, scheduling, or logging. 
  2. Less explicit code: Reducing boilerplate code is a good practice, but it can also make the code less explicit. Inexperienced developers may need help understanding how properties are initialized or how the constructor works.
  3. Compatibility issues: Integrating the new syntax of primary constructors seamlessly into an existing codebase could be challenging, especially if the codebase is large and complex.
  4. Limited control over access modifiers: A primary constructor offers less control over access modifiers than a traditional constructor. Hence primary constructors may be unsuitable in scenarios that require fine-grained control over accessibility.
  5. Learning curve: Because primary constructors are new to the C# programming language, your developers may need time to get up to speed using it, which could slow down the development of your project.

Using overloaded constructors with a primary constructor in C#

You can use overloaded constructors in any class that has a primary constructor. The following code snippet illustrates how you can create multiple constructors in a class having a primary constructor.

public class User(int id, string userName, string password) {     //Implement the members of the class here }// Other members of the class }

Using primary constructors in record types vs. classes in C#

While both record and classes support primary constructors, there are subtle differences in the behavior. For example, here is how you can define a record type that uses a primary constructor:

public record AuthorRecord(int Id, string firstName, string lastName, string address, string phone);

And here is how you can define a class that uses a primary constructor:

public record AuthorClass(int Id, string firstName, string lastName, string address, string phone);

You can create an instance of the above record type as shown below.

var authorRecord = AuthorRecord(int Id, string firstName, string lastName, string address, string phone);

However, if you attempt to assign values to the data members of the record type in the way shown below, you will encounter a compiler error.

authorRecord.Id = 1;

By contrast, you can create an instance of the above class type and assign values to its data members as shown below, without any compiler error.

var authorClass = AuthorClass(int Id, string firstName, string lastName, string address, string phone); authorClass.Id = 1;

Use dependency injection in primary constructors in C#

You can take advantage of primary constructors when implementing dependency injection in your C# application. Let us understand this with an example. The following code illustrates how we can implement constructor injection in C#.

public class AuthorService {     private readonly IAuthorRepository _authorRepository;     public AuthorService(IAuthorRepository authorRepository)     {         _authorRepository = authorRepository;     }     public async Task> GetAll()     {         return await _authorRepository.GetAll();     } }

Finally, note how primary constructors can be used to make your code crisper and cleaner, as shown in the following piece of code.

public class AuthorService (IAuthorRepository authorRepository) {     public async Task> GetAll()     {         return await authorRepository.GetAll();     } }

Although primary constructors were initially introduced in C# 9, they were limited to record types only. With C# 12, you can use primary constructors in record types, classes, and structures, helping you write boilerplate code that is cleaner and more concise.

Joydip Kanjilal is a Microsoft MVP in ASP.NET, as well as a speaker and author of several books and articles. He has more than 20 years of experience in IT including more than 16 years in Microsoft .NET and related technologies.

Copyright © 2024 IDG Communications, Inc.