C# 12: Primary Constructors

In November 2023, Microsoft released .NET 8.0, and since then you can use C# 12.

So, it might be a good time for you to learn about the new features of the programming language of the year 2023. To learn the features, I start with this post a small series of blog posts.

In addition to this series about C# 12, you might also see some new posts about C# 11 features that I haven’t blogged about yet, and of course about other topics, like .NET, GitHub, TypeScript etc. But now, let’s focus on C# 12.

If you have followed this blog for a while, you might have noticed that I didn’t blog much in the past months. I’m still here, but I was very busy with my open source project MvvmGen, with my day-to-day job, with conference talks, with the creation of Pluralsight courses and of course with 3 kids (Kudos to my wife for all the help).

The plan for the future is to write on this blog again on a regular basis. If there’s something specific you’d love to read about, let me know in the comments or via my contact form.

Thomas Claudius Huber in 2024

C# 12 – What’s New?

There are many new features in C# 12, like primary constructors for classes/structs, collection expressions, the possibility to alias any type with a using, default parameters in lambda expressions and ref readonly parameters. You will learn about all of these in this blog post series. Now let’s start and let’s take a look at primary constructors.

C# 12 or C# 12.0?
Good point. There where versions in the past that had a decimal place, like for example C# 7.3 or C# 8.0. But since Microsoft is releasing a new version of .NET every year, a new version of C# is released every year too. So today, there are less reasons than before to use a decimal place for C#, as we get a new version every year. And according to the table that you find on this official page about the C# language versioning, version 10 was the first version that did not use a decimal place in the official documentation. This means that also C# 11 and C# 12 don’t use a decimal place there.

At the same time, I wouldn’t say that calling it C# 12.0 is wrong. And actually, messages in the code editor still call it 12.0. So, I guess, 12 is the marketing number, 12.0 is what it’s called from the tech perspective. But that’s just a detail. We all know, versioning is never easy. :-)

So this means of course that C# 12 == C# 12.0 is true.
(If you’re a JavaScript dev, here we go: C# 12 === C# 12.0)

Understand Primary Constructors

With C# 12, Microsoft introduces primary constructors for classes and structs. In the code snippet below you find a simple Person class like you would create it before C# 12 – and of course it’s also still valid with C# 12. It has a constructor with a firstName parameter. That firstName parameter is used to initialize the FirstName property.

public class Person
{
    public Person(string firstName)
    {
        FirstName = firstName;
    }

    public string FirstName { get; set; }
}

Now, if you are on a .NET 8.0 project, it will support C# 12 by default. When you put the cursor on the Person class’ constructor in Visual Studio, you can press CTRL+. to open the suggestions provided by the light bulb. As you can see in the screenshot below, the very first suggestion says Use primary constructor.

Visual Studio’s light bulb suggests to use a primary constructor.

After the refactoring, you end up with a Person class like you see it below that uses a primary constructor. The primary constructor is defined directly after the class name. From a syntactic point of view it is just a list of parameters inside of a pair of parentheses. It does not have a body. You can use the constructor parameters in the body of the class, for example to initialize members like properties and fields. Below you can see that the FirstName property is initialized with the firstName constructor parameter.

public class Person(string firstName)
{
    public string FirstName { get; set; } = firstName;
}

So, you see, with primary constructors, your code gets much more compact. But what is C# actually doing behing the scenes? Let’s take a look at the generated Common Intermediate Language (CIL) code to which C# gets compiled. CIL is also known as just Intermediate Language (IL), and it was formerly called Microsoft Intermediate Language (MSIL).

Look at the Common Intermediate Language (CIL)

Let’s take a look at the CIL code of the Person class below that uses a primary constructor to initialize the FirstName property.

namespace CSharpTwelve;

public class Person(string firstName)
{
    public string FirstName { get; set; } = firstName;
}

To look at the CIL code, I use the Intermediate Language Disassembler tool (ildasm.exe) that comes with Visual Studio 2022. You can open it via a Developer Command Prompt. There you type ildasm and press ENTER. In the screenshot below you can see the code for the generated constructor. That code first sets the backing field of the FirstName property to the value of the firstName constructor parameter. That backing field is automatically generated for an auto property behind the scenes to store the property’s value, and the FirstName property is such an auto property. Next, the constructor calls the constructor of the object class, which is in this case the base class of the Person class, as you didn’t specify any other base class.

Generated CIL code

So, this means in other words that the generated constructor code first initializes all the members with the values of the constructor parameters, and after this, it calls the constructor of the base class.

Next, let’s take a look at calling a constructor of a base class.

Call a Constructor of a Base Class

Below you see a Developer class that inherits from a Person class. The Developer class defines a primary constructor with the parameters firstName and language. It passes the firstName parameter as an argument to the constructor of the Person class. To call the constructor of the Person class, a pair of parentheses is used after the Person class, and it contains the arguments. In the code snippet below that’s the firstName parameter.

public class Person(string firstName)
{
    public string FirstName { get; set; } = firstName;
}

public class Developer(string firstName, string language) : Person(firstName)
{
    public string WritesCodeWith => language;
}

Note that the base class constructor does not have to be a primary constructor. It can be any constructor. Below you see a code snippet in which the Person class defines a regular constructor. The Developer class has a primary constructor that calls the one of the Person class. This means, the Developer class below is exactly the same as the one in the code snippet above.

public class Person
{
    public Person(string firstName)
    {
        FirstName = firstName;
    }

    public string FirstName { get; set; }
}

public class Developer(string firstName, string language) : Person(firstName)
{
    public string WritesCodeWith => language;
}

So, you see, calling a base class constructor is very simple and very straight forward.

Next, let’s take a look at constructor overloads.

Create Constructor Overloads

Also with a primary constructor in place, you can define any number of constructor overloads in your class. But with primary constructors, there is one rule: Any other constructor must call the primary constructor with the this keyword. This is necessary to ensure that the parameters of the primary constructor are assigned when you access them anywhere in the class, for example to initialize a property.

In the code snippet below you see the Developer class with another constructor that has also a lastName parameter. Note how that constructor calls the primary constructor with this(firstName, language).

public class Developer(string firstName, string language) : Person(firstName)
{
    public Developer(
        string firstName,
        string language,
        string lastName) : this(firstName, language)
    {
        LastName = lastName;
    }

    public string WritesCodeWith => language;

    public string? LastName { get; set; }
}

Access Parameters in The Class

So far, you’ve seen in the code snippets of this blog post that you can access the parameters of the primary constructor to initialize properties. But the truth is that you can access them anywhere in the body of your class. This makes them also quite convinient when you build classes into which you want to inject dependencies. Let’s take a look at a simple example.

Let’s create an interface for a repository that can load developers:

public interface IDeveloperRepository
{
    Developer[] LoadDevelopers();
}

Next, let’s add an implementation. Of course, you could load the developers from a Web API, from a database or from anywhere else. As it doesn’t matter, I create them just in memory:

public class DeveloperRepository : IDeveloperRepository
{
    public Developer[] LoadDevelopers()
    {
        return [
            new Developer("Thomas","C#"),
            new Developer("Julia","TypeScript"),
            new Developer("Anna","Python")
        ];
    }
}

Now let’s say that you want to build a DeveloperViewModel class that needs to load developers. So, it depends on the IDeveloperRepository interface. Of course, you can define it with a primary constructor like you see it in the code snippet below. In the Load method, the constructor parameter with the name repository is used to load the developers and to print them to the console.

public class DeveloperViewModel(IDeveloperRepository repository)
{
    public void Load()
    {
        var developers = repository.LoadDevelopers();
        foreach (var dev in developers)
        {
            Console.WriteLine($"{dev.FirstName}: {dev.WritesCodeWith}");
        }
    }
}

This means, now the DeveloperViewModel can be used as you see it below. When you call its Load method, it loads the developers via the passed in DeveloperRepository instance and it writes the developers to the console.

var viewModel = new DeveloperViewModel(new DeveloperRepository());
viewModel.Load();

Again, What Is Going on Behind the Scenes?

In the previous section you created the DeveloperViewModel class that you see below. It contains a primary constructor with a repository parameter. The repository constructor parameter is used in the Load method to load some developers and to write them to the console.

public class DeveloperViewModel(IDeveloperRepository repository)
{
    public void Load()
    {
        var developers = repository.LoadDevelopers();
        foreach (var dev in developers)
        {
            Console.WriteLine($"{dev.FirstName}: {dev.WritesCodeWith}");
        }
    }
}

Now the question is: How is it possible that the DeveloperViewModel can access the repository constructor parameter in its Load method? Doesn’t this require that the parameter value is stored in a field of the class? Indeed. When you take a look at the class with ildasm.exe, you can see that a private field is generated behind the scenes. That field is used to store the value of the repository constructor parameter, so that it can be used in the Load method.

A field is generated for the repository constructor parameter

Note that the private field is not generated for every constructor parameter. The C# compiler decides if it is necessary or not. In this example, it is necessary, as you access the repository constructor parameter in the Load method. If you use a constructor parameter just to initialize a property, then no field will be created for that parameter.

But these are just details of what is going on behind the scenes with the C# compiler. For you as a developer, it means that you can use primary constructor parameters anywhere in the body of your class. If it is necessary to store the value in a field, the C# compiler will just do this.

Is the Generated Field Readonly?

If the C# compiler generates a field behind the scenes, that field is not readonly. This means that you can change the value of the field in the body of your class. In the following code snippet you can see a Person class with a firstName constructor parameter. As the parameter is used in the getter and setter of a property, a firstName field is generated. The generated firstName field is changed in the setter of the FirstName property. This works, as the field is not readonly.

public class Person(string firstName)
{
    public string FirstName
    {
        get => firstName;
        set => firstName = value;
    }
}

Instead of using a setter like in the code snippet above, you can change the field of course in any method. The code snippet below changes it in a ChangeFirstName method.

public class Person(string firstName)
{ 
    public void ChangeFirstName(string newName)
    {
        firstName = newName;
    }
}

Now, does this mean that primary constructors are not for you if you want to use readonly fields?

No, you can still use primary constructors, even if you need readonly fields. You can store the parameter value in a readonly field like in the code snippet below. As you can see, the readonly field can have exaclty the same name as the constructor parameter. If you do this, the C# compiler won’t generate an additional field for the constructor parameter if you want to use the field in the body of your class, like for example in a method.

public class Person(string firstName)
{
    private readonly string firstName = firstName;
}

When you declare a readonly field like in the code snippet above, you will get an error like in the screenshot below if you try to change the firstName field. Because now with the name firstName you’re referencing the readonly field that you defined, the C# compiler does not generate a field anymore.

The C# compiler also detects edge cases. What for example if you write code like in the following code snippet. As you can see, the firstName constructor parameter is stored in a _firstName readonly field (note that the name of this field starts with an underscore). But in the ChangeFirstName method, you accidentially access the firstName constructor parameter instead of the readonly _firstName field.

public class Person(string firstName)
{
    private readonly string _firstName = firstName;

    public void ChangeFirstName(string newName)
    {
        firstName = newName;
    }
}

When you do something like in the code snippet above, it means that the C# compiler generates also a firstName field behind the scenes that is then used in the ChangeFirstName method. But as you initialized the readonly field _firstName with the value of the firstName constructor parameter, your intention was probably definitely to use that readonly field _firstName in your class, and not the firstName constructor parameter. And exactly because of this, the C# compiler generates a warning for the code snippet above. You can see it in the screenshot below. As you can see, it says that the firstName constructor parameter is captured into the state of the type. This means that the C# compiler generates a firstName field to store that state respectively that value of the constructor parameter, as you access it in the ChangeFirstName method. In addition, the value of the firstName constructor parameter is also used to initialize a field, property, or event. Yes, you initialize the _firstName readonly field. So, it’s that field that you should use in the ChangeFirstName method, and when you do that, the warning goes away.

So, you see, working with primary constructors and readonly fields works as well.

Didn’t Primary Constructors Exist Already For Record Types?

Yes, you’re absolutely right. Primary constructors existed already since C# 9 for record types. But there’s a difference between primary constructors for records and for classes. For record types, the C# compiler creates a public property for every constructor parameter, for classes the C# compiler does not do this.

Take a look at the Person record in the code snippet below. It has a primary constructor with a FirstName parameter. For this record, the C# compiler generates a public FirstName property that gets initialized with the value of the FirstName constructor parameter.

public record Person(string FirstName);

Primary constructors for classes don’t generate public properties behind the scenes.

Summary

You learned in this blog post about primary constructors for classes and structs that were introduced with C# 12. They let you define your classes in a more compact way than ever before. I also believe that this new syntax is more readable, especially when your class has only a single constructor to initialize some members.

I hope you enjoyed reading this blog post about C# primary constructors.

In the next blog post, you will learn about another C# 12 feature: Collection Expressions.

Share this post

Comments (3)

  • Rob Slamey Reply

    One important difference that is often overlooked is that the field that is created is not, and cannot, be marked as readonly. Therefore is no compiler support to prevent the field from being changed, as is typical with “normal” constructors assigning to private readonly fields.

    As such, I would not recommend using Primary constructors.

    March 13, 2024 at 10:40 pm
    • Thomas Claudius Huber Reply

      Hi Rob,

      great input! I added an additional section to this blog post for you and others: “Is the Generated Field Readonly?”. It describes a few things about primary constructors and readonly fields. Hope this helps.

      Thank you,
      Thomas

      March 14, 2024 at 10:14 am
  • C# 12: Alias Any Type – Thomas Claudius Huber Reply

    […] Primary Constructors […]

    March 27, 2024 at 3:47 pm

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.