.NET 8 and C# 12 — Primary Constructors

Henrique Siebert Domareski
4 min readNov 22, 2023

--

Primary Constructors is a feature that was already present in C# 9 but it was restricted to record types, and now with C# 12 this feature was extended to any class and struct. In this article, I present how to use the Primary Constructor in a class.

Primary Constructor is an easier way to create a constructor for your class or struct, by eliminating the need for explicit declarations of private fields and constructor bodies that only assign parameter values to those fields. With Primary Constructor you can add parameters to the class declaration and use these values in the class body.

Before showing how to use Primary Constructor, let’s have a look at the default way of using constructors. This is how we would create the Book class with a constructor that initialises some properties:

  • On lines 3 up to 7, there are the class properties.
  • On lines 9 up to 18, there is the constructor.

For demonstration purposes, I created a console application with .NET 8, and I configured the LangVersion in the .csproj file with the preview value:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>

</Project>

Now in C# 12, with Primary Constructor, we can achieve the same result but with fewer lines of code:

  • On line 1, there is the class declaration using Primary Constructor, which has three properties: id, title and ratings.
  • On lines 3 and 4 there are two properties that are read-only, and whose values will be received via Primary Constructor.
  • On line 6 there is a property on which will be possible to read and write some value to it.
  • On line 8 there is a property that will retrieve the average of rating for the book.

We can also have multiple constructors, and for that, it’s always necessary to use the this(…) initializer (which will call the main constructor), to call another constructor on the same class or struct; This ensures that the primary constructor is always called and all the data necessary to create the class is present. For example:

  • On line 1, there is the class declaration using Primary Constructor.
  • On line 3, there is a constructor which receives the id and the title.
  • On line 5 there is a constructor that has no parameters, and it initialises the object with default values (id 99 and title “Demo book”).

Now let’s initialise some instances of these classes:

var book1 = new BookDefault(
1,
"The Lord of The Rings the Fellowship of the Ring",
new List<decimal>() { 5, 4, 4, 5 });

var book2 = new Book(
2,
"The Lord of The Rings the Two Towers",
new List<decimal>() { 4, 3, 2, 4 });
book2.Pages = 352;

var book3 = new Book(
3,
"The Lord of the Rings the Return of the King");

var book4 = new Book();

Console.WriteLine($"{nameof(book1)}: {JsonSerializer.Serialize(book1)}");
Console.WriteLine($"{nameof(book2)}: {JsonSerializer.Serialize(book2)}");
Console.WriteLine($"{nameof(book3)}: {JsonSerializer.Serialize(book3)}");
Console.WriteLine($"{nameof(book4)}: {JsonSerializer.Serialize(book4)}");
  • For book1, we are using the BookDefault class, which does not use Primary Constructor (it uses the default way to use constructor). For the other instances, we are going to use the class that has Primary Constructor (the Book class).
  • For book2, we initialise the class with the id, title and rating, and after that we set the number of Pages. Note that it will not be possible to set the values for properties such as Id, Title or Ratting, cause you will get the following Compiler Error: Property or indexer ‘property’ cannot be assigned to — it is read only.
  • For book3, we create an instance of the class with only the id and the title, and without defining the rating, so it will use the second constructor, and the default value for rating will be 0.
  • For book4, we create an instance of the class without passing any values to it, so it will use the third constructor, which has no properties, and the default values will be added to it.

This will be the output:

book1: {"Id":1,"Title":"The Lord of The Rings the Fellowship of the Ring","Pages":0,"AverageRating":4.5}
book2: {"Id":2,"Title":"The Lord of The Rings the Two Towers","Pages":352,"AverageRating":3.25}
book3: {"Id":3,"Title":"The Lord of the Rings the Return of the King","Pages":0,"AverageRating":0}
book4: {"Id":99,"Title":"Demo book","Pages":0,"AverageRating":0}

Dependency Injection

You can also make use of Primary Constructor for Dependency Injection, and to do that, you need to follow the same approach as presented in the previous example. Before demonstrating how to do it, let’s have a look at how we generally do Dependency Injection:

  • On line 1, there is the class declaration.
  • On line 3, there is a private property for the repository class.
  • On line 5, there is a constructor for the service class, which receives the IBookRepository via DI.
  • On line 7, it is initialised the _bookRepository with the provided bookRepository parameter.

With Primary Constructor, we can do the DI together with the class declaration, making the code cleaner and reducing the number of lines, for example:

  • On line 1, there is the class declaration with Primary Constructor which allows you to pass the bookRepository via dependency injection, avoiding boilerplate code.

Conclusion

Primary Constructor for class and struct is a new feature of C# 12, which allows you to have a constructor in an easier way. As presented, it’s also possible to use Primary Constructor for DI, reducing the number of lines of your code.

This is the link for the project in GitHub: https://github.com/henriquesd/DotNet8Examples

If you like this demo, I kindly ask you to give a ⭐️ in the repository.

Thanks for reading!

--

--