Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 3 de diciembre de 2019
.NET Core Hace poco estaba revisando un proyecto y me topé con un problema derivado de un uso extraño de los expression bodied members, y creo que es interesante comentarlo porque, aunque probablemente la mayoría no os vayáis a encontrar con esto, seguro que puede venir bien a alguien que no tenga del todo claro cómo y cuándo se debe utilizar esta sintaxis.

Pero comencemos desde el principio...

Evolución de la inicialización de propiedades

Como sabemos, el siguiente código es la forma "tradicional" de definir una propiedad de sólo lectura con backing field, inicializada a un valor concreto:
public class Example
{
    private int _theNumber = 42;
    public int TheNumber
    {
        get
        {
            return _theNumber;
        }
    }
}
C# 3 introdujo las propiedades automáticas, que eliminaban la necesidad de definir explícitamente un backing field. Esto simplificaba algo el código, pero su inicialización debíamos implementarla en el constructor:
public class Example
{
    public int TheNumber { get; }
    public Example()
    {
        TheNumber = 42;
    }
}
Afortunadamente, C# 6 introdujo la posibilidad de inicializar directamente propiedades automáticas junto a su declaración, lo que permitía obtener el mismo resultado escribiendo un código más compacto:
public class Example
{
    public int TheNumber { get; } = 42;
}
Y al mismo tiempo, C# 6 introdujo los expression bodied members, que luego fueron extendidos en C# 7 para permitir una sintaxis aún más concisa en determinados escenarios. Usando esta característica, el código anterior podríamos dejarlo en:
public class Example
{
    public int TheNumber => 42;
}

¿Y cuál es el problema?

Ciertamente esta forma de inicializar propiedades usando expression bodied members es muy cómoda y compacta, pero puede confundir un poco si no sabemos exactamente qué estamos haciendo.

Imaginemos ahora el siguiente código, donde se utiliza => con el objetivo de inicializar una propiedad:
public class Example: IDisposable
{
    public DatabaseConnection Connection => Database.OpenConnection(); // Mal!
    public void AddOneHundredCustomers()
    {
        for(var i=1; i <= 100; i++)
        {
            Connection.AddCustomer(new Customer() { Id = i })
        }
    }
    public void Dispose()
    {
        Connection.Dispose();
    }
}
Aunque la intención en el uso de => era inicializar la propiedad, en realidad lo que estamos haciendo es instanciar un objeto DatabaseConnection cada vez que se accede a la propiedad Connection, tanto en el cuerpo de AddOneHundredCustomers() como en Dispose(). Es decir, durante la ejecución del siguiente código instanciaríamos 101 conexiones, y liberaríamos sólo la última de ellas:
using (var example = new Example())
{
    example.AddOneHundredCustomers();
}
Lo importante aquí es entender que al usar esa sintaxis no estamos inicializando nada, sino indicando la expresión que retornará el valor de la propiedad cada vez que accedamos a ella.

Entonces, ¿cuál sería la forma correcta de hacerlo?

Pues como siempre ;) Por una parte declaramos la propiedad y luego la inicializamos en el constructor de la clase, o bien utilizamos los inicializadores de propiedades, como en el siguiente ejemplo:
public class Example : IDisposable
{
    public DatabaseConnection Connection { get; } = Database.OpenConnection(); // Ok
    ...
}
En conclusión, cuando se introducen novedades al lenguaje, a los desarrolladores nos encanta comenzar a utilizarlas enseguida, a veces sin llegar a entender bien qué es lo que estamos haciendo.

Y la moraleja de esta historia es que cualquier novedad, por insignificante que parezca, requiere dedicar al menos unos minutos de estudio y asimilación, pues esto puede marcar la diferencia entre una aplicación que funciona y otra que no.

Publicado en Variable not found.

5 Comentarios:

jorge.serrano dijo...

¡Muy buena entrada y muy buen apunte José María!

Sólo por agregar algo más de información, comentar que la declaración como variable también es similar al de la propiedad con get que indicas en tu entrada.

Es decir, el código válido:

public DatabaseConnection Connection { get; } = Database.OpenConnection(); // Ok

Se podría indicar también como variable en lugar de propiedad de la forma:

public DatabaseConnection Connection = Database.OpenConnection(); // Ok

Y obviamente, también como:

public readonly DatabaseConnection Connection = Database.OpenConnection(); // Ok

Recordemos que expression bodied members genera en IL un getter que devuelve lo que se indique cada vez que se le llame.
Normalmente y si es un valor constante, no cambiará, pero aquí es una llamada a un método, que en este caso abre una conexión, así que cada vez que lo llamamos, el getter llama al método/función de crear conexión.

Si lo que devolviera fuera un estático, no tendríamos problemas en hacerlo tal y como indicas, es decir, de la forma:

public DatabaseConnection Connection => Database.OpenConnection();

Digo todo esto, porque no vayamos a pensar que esta forma es incorrecta.

Lo que debemos tener en consideración es cómo funciona y a partir de ello, aplicarlo correctamente.

Tal y como muy bien dices, que tengamos cuidado "que cualquier novedad, por insignificante que parezca, requiere dedicar al menos unos minutos de estudio y asimilación".

Un saludo y ¡muy buena entrada!

José María Aguilar dijo...

Hola, Jorge!

Me alegra verte por aquí :)

Efectivamente, los expression bodied members no son peligrosos en sí mismos, lo "peligroso" es usarlos de forma incorrecta, que es lo que quería remarcar en el post.

Si los usas para asignar una constante no ocurrirá nada raro, o si la expresión retornase una referencia a un singleton o un miembro estático. Lo importante es simplemente saber lo que estás haciendo :)

Muchas gracias por el aporte!


jorge.serrano dijo...

¡Así es!
Estamos alineados :)

Mi comentario era para remarcar a los lectores de tu entrada precisamente eso, que no se confunda nadie y que utilizar las expresion bodied members no son ideas del diablo, sino que como muy bien indicas, debemos ser conscientes de lo que hacemos y sus consecuencias.

Muy buen aporte el tuyo para levantar la ceja a más de uno. :)

Abrazos!

Unknown dijo...

Muy interesante y sobre todo claro en los ejemplos :). Muy buen articulo!!

José María Aguilar dijo...

Muchas gracias!