Since the release of .NET 7 and C# 11 last november, the team at Microsoft responsible for the C# language has continued it’s development towards C# 12. Last january a first version of C# was released with the proposal of primary constructors implemented. It is not sure yet if this feature will make it to the final release of C# 12 but in this blog I would like to take a look at it.

What are primary constructors?

When writing classes that have dependencies you often write very simple constructors that always do the same thing. They accept the parameters and set them to some private backing fields so they can be used by the methods in that class. An example of this can be:

public class OrganizationService
{
    private readonly IOrganizationRepository repository;
    private readonly IDateHelper dateHelper;

    public OrganizationService(IOrganizationRepository repository, IDateHelper dateHelper)
    {
        this.repository = repository;
        this.dateHelper = dateHelper;
    }
}

This is something you do in a lot of your classes. With primary constructors the amount of code can be reduced and the result is more simple code.

What will this look like in C# 12?

The proposal to add primary constructors in C# 12 would make your code look like this:

public class OrganizationService(IOrganizationRepository repository, IDateHelper dateHelper)
{    
}

The first thing you notice is that there are some things removed:

  • The private backing fields are gone
  • The constructor declaration and passing the parameters into the backing fields is gone

Next to this there is something added in the class declaration. Behind the name of the class the parameters for the primary constructor are immediately declared. The result of this is almost the same as the first code example with the normal constructor. This is because primary constructors work with the help of lowering. Lowering is the part of the C# compiler where more lower level C# code is generated based on the given C# code (want to know more about this then check out my presentation about lowering from the .netassemble event last year). To see what code is generated you can use the website sharplap.io. I have made an example there that shows how the primary constructor is lowered to a normal constructor with private backing fields:

sharplab.io demo.

Looking at the demo above you can see that a normal class with a constructor as we know it is generated. It looks like this:

public class AnimalService
{
    [CompilerGenerated]
    private string <animalName>PC__BackingField;

    [CompilerGenerated]
    private int <minWeight>PC__BackingField;

    [System.Runtime.CompilerServices.NullableContext(1)]
    public AnimalService(string animalName, int minWeight)
    {
        <animalName>PC__BackingField = animalName;
        <minWeight>PC__BackingField = minWeight;
        base..ctor();
    }
}

The only difference with my own code that I noticed is that the backing fields are NOT readonly. For the rest it is exactly the same.

How will this work with multiple constructors?

As the name implies the primary constructor will be the default but you can add multiple other constructors. However, there is one condition and that is that you must use the this keyword to call the primary constructor. So, when we add another constructor to the previous example the code would look like this:

public class AnimalService(string animalName, int minWeight)
{    
    public AnimalService(string otherParameter) : this("DefaultName", 83)
    {
        // do something with the otherParameter
    }    
}

Isn’t this feature already available in C# using record classes?

Yes, it is. But it is slightly different and that makes it also a bit confusing. In C# 9 this feature was added to record classes. Since record classes are meant to store data (mostly simple POCO’s) the implementation is slightly different there. For example when you define a record like this:

public record AnimalRecord(string AnimalName, int MinWeight)
{    
}

The following code is generated by the C# compiler using lowering:

public class AnimalRecord : IEquatable<AnimalRecord>
{
    [CompilerGenerated]
    private readonly string <AnimalName>k__BackingField;
    [CompilerGenerated]
    private readonly int <MinWeight>k__BackingField;
    public string AnimalName
    {
        [CompilerGenerated]
        get
        {
            return <AnimalName>k__BackingField;
        }
        [CompilerGenerated]
        init
        {
            <AnimalName>k__BackingField = value;
        }
    }
    public int MinWeight
    {
        [CompilerGenerated]
        get
        {
            return <MinWeight>k__BackingField;
        }
        [CompilerGenerated]
        init
        {
            <MinWeight>k__BackingField = value;
        }
    }
    public AnimalRecord(string AnimalName, int MinWeight)
    {
        <AnimalName>k__BackingField = AnimalName;
        <MinWeight>k__BackingField = MinWeight;
        base..ctor();
    }
 
    ......
}

You will immediately see the difference. The parameters are not only generated as private backing fields but also as public properties. This is done because that is what you most of the time want when using records in C#.

Conclusion / my opinion on primary constructors

Looking at this proposal and playing around with it I am positive about this new C# feature. It can reduce the amount of code you have to write and make your code more easy to read. The only downside I see for now is the difference in implementation between normal classes and record classes. In both cases you express yourself in the same way but the code generated behind the scenes is different. This is something you have to be aware of.

What do you think about primary constructors? Let me know by leaving a comment or send me a message on LinkedIn or via e-mail.

Feedback or questions

Do you have any feedback or questions regarding this blog? Feel free to contact me!

References