Primary constructors were introduced to the C# landscape within version 12. A primary constructor is a new shorthand syntax that you can use when creating either a class or a struct that will reduce the amount of boilerplate code that you need to write.

Within this guide, you will learn how to start using primary constructors, as well as the architectural caveats that you need to be aware of before you start adopting them everywhere. Read on to master this new feature in under 10 minutes 🔥🔥🔥

Before we deep-dive into this new syntax, let's first review how we used to create a class with a constructor:

For this guide, you don't need to get too hung up on what the code is actually doing, the main thing to appreciate is that we have a class with a constructor. I need to inject an argument of type IUmbracoContextFactory into the class (assume using dependency injection). In order to use this dependency within the class, I need to create a constructor argument and then within the constructor, map the incoming dependency to a private read-only field contained within the class. Without doing this, the state of the passed-in IUmbracoContextFactory object would be lost within that class

A primary constructor will allow you to do the above, however, using less code. Over the last few C# releases, there have been a number of similar updates to the language. Over the years the .NET team seems to be focusing on reducing the amount of boilerplate and cruft code that developers have previously been forced to write. While these changes arn't groundbreaking, they do make for cleaner codebases. Before looking at the primary constructor, let's refactor this code to use some of these newer conventions:

These updates allowed us to remove some nesting and need {}, however, the big blot in the class is the code required to pass in the dependencies. The primary constructor allows you to skip having to write a specific method to have the constructor, instead, you can define a constructor with your class definition, like this:

This code is much cleaner. We still pass in IUmbracoContextFactory, however, now it is automatically class-scoped and we can access it anywhere we want. Let's say we want to expose it as a public property of the class, we would do this:

This code is much cleaner, compared to how we used to write it. Within my projects, about 80% to 90% of the code within my constructors simply mapped incoming arguments to either private variables or public properties within my class. If your class is only doing the same, the primary constructor will:

  • Reduce the amount of boilerplate code you need to write in the construction
  • Make your code easier to read
  • Make your code easier to maintain

Obviously, not all classes have simple constructors, sometimes you need to add logic within your constructors. In my opinion, this is where primary constructors fall down a little. One critique of .NET over the years is that with each new update, we now have more ways of doing the same thing which can make your codebase less consistent and harder to understand. Sadly, the same is true for primary constructors as they are not ideal in all situations. What we will do now is look at some of the caveats in detail.

Readonly Arguments

If you look at the first code snippet, you can see my property was converted to a readonly property. The snippet below would not compile as you can not add readonly within the constructor,

When using a primary constructor you would still need to declare a new field or property to store it within your class.

This is not the end of the world, however, there is an argument that if you need to do this on several incoming parameters, your code might make more sense if you grouped it all together. Do you think this code is cleaner?

Constructor Inheritance

The next thing to consider is inheritance. Often you might want to create constructor overloads, or, pass incoming parameters down into a base constructor. Currently, primary constructors do have limits around argument access within different constructor scopes:

This code will not compile and it will throw an error:

Can not use a primary constructor argument in this context

While you can not access primary constructor arguments within a constructor overload, you can pass different values down. This code would compile fine:

This issue around accessibility can also occur if you define fields within your classes with the same name as a primary constructor argument. Read the code below and without using a debugger, tell me what it's doing:

This code compiles so it would be up to you to prevent it within a code review process. Again, not a deal breaker but it could make code harder to read rather than easier!

Records Vs Classes

Another criticism of primary constructors is that it might add confusion to your code if you use the record type a lot. In case you were not aware, the record type has a specific constructor shorthand syntax whose aim is to provide a concise way to create public read-only properties on records.

In simple terms when you use construct arguments in a record it looks very similar to a primary constructor in a class, however, there is a big difference in terms of intention! A record type arguments are automatically generated as public properties, whereas primary contractors used on classes and structs are only class-scoped private variables.

When you instantiate a record the compiler would only create a single variable to store the public property, whereas when the compiler runs the code below it will add two things to memory:

As the only difference in the code is that the type is a record and the incoming arguments start with a capital letter, it might be easy to miss this in a code review.

Required Parameters

Within C# 11, the required modifier was introduced. The required modifier allows you to add the required keyword onto properties. When this happens the compiler will force you to include the parameter when you instantiate the containing class, however, you can supply the value without having to add it as a constructor argument! You can see an example of how you could implement required below:


With new parameter constructors, you can not use the required keyword when you pass the parameter in. If you want to make sue of this feature, instead you could do something like this:

To me, this code feels hacky just so we can use required.

If most of your constructor code is simply dependent on mapping, primary constructors are great and they simplify your codebase. When things are more complex, you will be forced into adopting a mix-and-match pattern of techniques within your codebase.

My personal coding preference is to have a single agreed-upon way of solving every type of code implementation detail a team might face to ensure consistency. The caveats that come with the primary constructor in more complex use cases mean that this standard will be harder to adopt. In some situations, I think using a primary constructor will result in code that is more bloated and confusing to understand compared to building a constructor normally.

Overall, I think primary constructors are a great feature, however, I wish the .NET team could have better resolved some of those caveats first!

Happy Coding 🤘