Dilema con los primary constructors

04 Feb 2024 10 min (1) Comentarios

En este post vamos a tratar un tema que lleva un tiempo intrigando dentro de C# Como creador de contenido, debo usar primary constructors o no? 

 

 

1 - Qué son los primary constructos? 

La primera parada que debemos hacer es para ver y comprender que son los primary constructors? Con la llegada de los récords, C# los introdujo, pero han empezado a hacerse populares ahora, debido a que desde .NET 8 cualquier clase puede tener un primary constructor.

 

Un prímary constructor no es más que la forma que nos da C# de poder definir las propiedades de una clase, sin poner mucho código.

Como he dicho esto lo hemos visto en los records, por ejemplo este record:

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

Es lo mismo que tener esto: 

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

Como vemos tenemos una reducción obvia del tamaño del objeto, bien, pues desde net 8 podemos realizar la misma acción con las clases. 

Aquí podemos ver un ejemplo donde tenemos nuestro ItemDto esta vez, en forma de clase:

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);

OJO! Estas dos formas no son equivalentes, ya que cuando usamos primary constructors en clases, las propiedades son por defecto privadas, tanto el getter como el setter, así que si instanciamos una variable de ese tipo, no vamos a poder acceder a sus miembros, para ello, tendremos que poner una propiedad pública que así lo permita. 

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;
    }
}

 

Si alguno de vosotros lleva programando en C# unos 15 años o más, seguramente estéis teniendo pesadillas ahora mismo, ya qué así es como se definían las propiedades hace pues eso, casi dos décadas. 

 

Que no, que es broma, osea es así pero podemos utilizar record class que funciona igual:

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

De esta forma podemos acceder a los miembros de forma pública cuando el objeto es instanciado, en el caso de la record class el setter, igual que en un record también es init;

 

 

1.1 - Primary constructors en clases servicio

 

Quizá no has caido pero los “servicios”o “casos de uso”, o llamalos como quieras son clases, lo que quiere decir que podemos utilizar primary constructors, y aquí sí que me parece un gran avance, ya que desde la llegada de dependency injection, en la gran mayoría de casos utilizamos el constructor de nuestro “servicio” simplemente para inyectar -https://www.netmentor.es/entrada/inyeccion-dependencias-scoped-transient-singleton-los servicios que vamos a necesitar.

 

Vayamos al caso de uso que vimos en el post sobre MediatR, donde inyectamos dos servicios adicionales, lo que quiere decir que tenemos dos dependencias, el servicio se ve tal que así:

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)
    {
       ///Lógica
    }
}

Aquí es donde la magia de los primary constructors actúa, nos permite quitar el constructor y simplemente definir en el nombre de la clase unos paréntesis y las propiedades que vamos a inyectar. 

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

Como vemos hemos reducido considerablemente la cantidad de código, en este caso en concreto hemos quitado 8 líneas.

 

 

2 - Las convenciones de nombres

 

Ya he hablado varias veces sobre los problemas que tiene Microsoft a la hora de asignar nombres, ya sea a sus frameworks, las versiones, o a paquetes como Aspire, el cual es un buen paquete pero el nombre es terrible; Pues para los primary constructors el problema es similar, no es el nombre en si, sino en la nomenclatura a utilizar.

 

Básicamente tenemos 3 formas diferentes para elegir nuestra convención de nombres, en mi opinión cuando tenemos clases/objetos que representan objetos de dominio y no flujos de trabajo, el problema no es muy grande, ya que utilizar pascal case tiene sentido (la segunda opción de abajo) pero cuando vamos a utilizar primary constructors dentro de un caso de uso la cosa cambia. 

 

Tenemos las siguientes opciones:

 

La primera, la de siempre, cuando tenemos un constructor “clasico”: 

  • Los fields son barra baja y luego camel-case.
  • Las propiedades son pascal-case
  • Y los parametros son camel-case:
public class UpdateItem
{

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

    //propiedad DefaultItemId
    public int DefaultItemId => 0;


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

    //parametros itemToUpdate
    public async Task<bool> Execute(ItemDto itemToUpdate)
    {
       ///Lógica
    }
}

 

La segunda forma es como los record class, pascal-case, eso quiere decir con la primera mayúscula y luego la primera letra de cada palabra es mayúscula:

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

 

Como digo, este caso es equivalente a un record class, pero al no ser record class no hay nada público.

 

Pero aún así nos va a traer algún problema, porque técnicamente el IDE entiende esto como un parámetro, y no estamos cumpliendo las normas de los parámetros

 

Como tercera y última forma, utilizar la recomendada/sugerida por el IDE, los parámetros son en minúscula por lo tanto en nuestro primary constructor vamos a inyectar las propiedades en minúscula.

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

A primera vista puede parecer la más lógica de utilizar con primary constructors, y en mi opinión así es, pero a su vez está rompiendo una regla no escrita de utilizar private readonly con barra baja y camel-case para todos los servicios que inyectamos en una clase.

Por lo tanto esto puede significar que a la hora de hacer revisiones de código tengamos dudas o no estemos seguros de si algo está inyectado o no. de primeras algo que no parece muy importante, pero que a la larga nos puede consumir muchas horas de trabajo. 

 

Por supuesto siempre está la opción de utilizar _servicio a la hora de utilizar el primary constructor, pero no estoy muy seguro de que vaya a ser popular, la verdad. 

 

 

3 - Debo usar primary constructors?

El motivo principal por el que he escrito este post es para hacer un poco de brainstorming y poner las ideas que voy teniendo en la cabeza sobre el papel, y si bien es cierto no es un post complejo, es contenido que quería sacar de mi cabeza, principalmente para ver lo que la comunidad opina. 

 

En el entorno laboral, voy a usar primary constructors, la convención del nombre… pues no lo tengo tan claro, pero la reducción  de código es obvia, y menos código que mantener siempre es bueno. 

 

El problema viene a la hora de crear el contenido del blog/canal, siempre intento tener todo a la última, pero también pienso que el uso de los primary constructors puede confundir a la gente nueva, ya que la gran mayoría de tutoriales en internet o cursos que van a realizar van a tener el formato “antiguo/normal”. 

Entonces, si hoy fuera a hacer un “curso completo sobre web api en C#”, qué haría? 

Posiblemente primary constructors.

 

Valoraré durante las siguientes semanas que hacer, pero lo más probable es que me mueva a los primary constructors, iremos viendo. 

 

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 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café