Dilemma with Primary Constructors

In this post we will address a topic that has been intriguing in C# for a while now: As a content creator, should I use primary constructors or not?

 

 

1 - What are primary constructors?

The first stop we need to make is to see and understand what primary constructors are. With the introduction of records, C# brought them, but they've started to become popular now since, starting with .NET 8, any class can have a primary constructor.

 

A primary constructor is simply the way C# enables us to define the properties of a class without writing much code.

As I said, we've seen this in records, for example this record:

public record ItemDto(int Id, decimal Price, string Title);

This is the same as having this:

public record ItemDto
{
    public string Id { get; init; }
    public decimal Price { get; init; }
    public string Title { get; init; }
}

As we can see, we have an obvious reduction in object size. Well, since net 8 we can do the same with classes.

Here we can see an example where we have our ItemDto, this time as a class:

public class ItemDtoDefault
{
    public string Id { get; set; }
    public decimal Price { get; set; }
    public string Title { get; set; }
}

public class ItemDtoPrimary(int Id, decimal Price, string Title);

Note! These two forms are not equivalent, because when we use primary constructors in classes, the properties are private by default, both getter and setter. So if we instantiate a variable of this type, we won't be able to access its members. For that, we need to add a public property to allow it.

public class ItemDtoPrimary(int id, decimal price, string title)
{
    public int Id
    {
        get => id;
        set => id = value;
    }

    public decimal Price
    {
        get => price;
        set => price = value;
    }

    public string Title
    {
        get => title;
        set => title = value;
    }
}

 

If any of you have been programming in C# for 15 years or more, you're probably having nightmares right now, since this is how properties were defined, oh, nearly two decades ago.

 

Just kidding, I mean it is like that, but we can use record class which works the same way:

public record class ItemDto(int Id, decimal Price, string Title);

This way we can access the members publicly when the object is instantiated. In the case of a record class the setter, just like in a record, is also init;

 

 

1.1 - Primary constructors in service classes

 

Maybe you haven't noticed, but "services" or "use cases", or whatever you want to call them, are classes, meaning we can use primary constructors. And here, I do think it's a big step forward, since with the arrival of dependency injection, in the vast majority of cases we use the constructor of our "service" simply to inject -https://www.netmentor.es/entrada/inyeccion-dependencias-scoped-transient-singleton-the services that we're going to need.

 

Let's look at the use case we saw in the post about MediatR, where we inject two additional services, meaning we have two dependencies. The service looks like this:

public class UpdateItem
{
    private readonly IDatabaseRepository _databaseRepository;
    private readonly NotifyWishlist _notifyWishlist;

    public UpdateItem(IDatabaseRepository databaseRepository, NotifyWishlist notifyWishlist)
    {
        _databaseRepository = databaseRepository;
        _notifyWishlist = notifyWishlist;
    }

    public async Task<bool> Execute(ItemDto itemToUpdate)
    {
       ///Logic
    }
}

This is where the magic of primary constructors comes in, they allow us to remove the constructor and simply define the needed properties in parentheses after the class name.

public class UpdateItem(IDatabaseRepository databaseRepository, NotifyWishlist notifyWishlist)
{
    public async Task<bool> Execute(ItemDto itemToUpdate)
    {
        ///Logic
    }
}

As we can see, we've significantly reduced the amount of code, in this case, we've removed 8 lines.

 

 

2 - Naming conventions

 

I've talked several times about Microsoft's issues with assigning names, whether it's their frameworks, the versions, or packages like Aspire, which is a good package but has a terrible name. With primary constructors, the problem is similar, it's not the name itself but the nomenclature to use.

 

Basically, we have three different ways to choose our naming convention. In my opinion, when we have classes/objects representing domain objects and not workflows, the problem isn't too big since using PascalCase makes sense (the second option below), but when we're going to use primary constructors within a use case, things change.

 

We have the following options:

 

The first one, the usual one, when we have a "classic" constructor:

  • Fields start with underscore and then camelCase.
  • Properties are PascalCase
  • Parameters are camelCase:
public class UpdateItem
{

    //Field _notifyWishlist
    private readonly IDatabaseRepository _databaseRepository;
    private readonly NotifyWishlist _notifyWishlist;

    //property DefaultItemId
    public int DefaultItemId => 0;


    //parameters databaseRepository
    public UpdateItem(IDatabaseRepository databaseRepository, NotifyWishlist notifyWishlist)
    {
        _databaseRepository = databaseRepository;
        _notifyWishlist = notifyWishlist;
    }

    //parameters itemToUpdate
    public async Task<bool> Execute(ItemDto itemToUpdate)
    {
       ///Logic
    }
}

 

The second way is like record class, PascalCase, which means starting with a capital letter and then every word's first letter is uppercase:

public class UpdateItem(IDatabaseRepository DatabaseRepository, NotifyWishlist NotifyWishlist)
{
  ...
}

 

As I said, this case is equivalent to a record class, but since it's not a record class, nothing is public.

 

But it still brings some problems, because technically the IDE sees this as a parameter, and we're not following the standards for parameters.

 

As a third and final way, use what's recommended/suggested by the IDE: parameters are lowercase, so in our primary constructor we'll inject properties in lowercase.

public class UpdateItem(IDatabaseRepository databaseRepository, NotifyWishlist notifyWishlist)
{
  ...
}

At first glance this might seem the most logical to use with primary constructors, and in my opinion, it is. But at the same time, it breaks an unwritten rule of using private readonly with underscore and camelCase for all services we inject into a class.

So this might mean that when doing code reviews we have doubts or we're not sure if something is injected or not. Initially, it might seem not very important, but over time it can cost us many hours of work.

 

Of course, there's always the option of using _service when using the primary constructor, but I'm not too sure that's going to be popular, honestly.

 

 

3 - Should I use primary constructors?

The main reason I wrote this post is to brainstorm a bit and get the ideas I've had in my head down on paper. And while it's true this is not a complex post, it's content I wanted to get out of my head, mainly to see what the community thinks.

 

In the work environment, I am going to use primary constructors. Which naming convention... I'm not so sure, but the code reduction is obvious, and less code to maintain is always good.

 

The problem comes when creating blog/channel content. I always try to keep everything up-to-date, but I also think the use of primary constructors can confuse newcomers, since most tutorials on the internet or courses they take will use the "old/normal" format.

So, if today I were to do a "complete course on web API in C#", what would I do?

Most likely, primary constructors.

 

I'll consider what to do over the next few weeks, but it's most likely I'll move to primary constructors; we'll see.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café