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, 29 de junio de 2021
.NET Core

Me encanta que el lenguaje C# vaya introduciendo características que consiguen que cada vez tengamos que escribir menos para conseguir lo mismo, y, sobre todo, si la legibilidad posterior del código no se ve comprometida.

Uno de estos casos son los recientes target-typed new expressions, o expresiones new con el tipo del destino, que básicamente permite evitar la introducción del tipo de datos al usar el constructor de una clase, siempre que el compilador sea capaz de inferirlo por su contexto.

Vamos a echarle un vistazo en este post.

Redundancy.... redundancy everywhere...

Redundancia... redundancia por todas partes...

En las primeras versiones de C# era obligatorio indicar el tipo de las variables al declararlas, incluso cuando las inicializábamos directamente. Antes de C# 3.0, teníamos que usar instrucciones como las siguientes para asignar nuevas instancias a variables en el interior de un bloque de código:

Invoice invoice = new Invoice(123);
Dictionary<string, Person> people = new Dictionary<string, Person>();

Está claro que en las asignaciones del bloque anterior hay mucha redundancia: si ya sabemos que la variable invoice es de tipo Invoice o que people es un diccionario porque estamos usando su constructor, ¿para qué es necesario indicar el tipo de las variables?

Esto se solucionó en 2007 con la llegada de C# 3, que entre otras muchas maravillas, introdujo las variables locales de tipado implícito. Esto nos permitió aprovechar la inferencia de tipos para simplificar el código sin restar legibilidad:

var invoice = new Invoice(123);
var people = new Dictionary<string, Person>();

Sin embargo, aún quedaban escenarios similares por mejorar, por ejemplo en la declaración de campos o propiedades de una clase, donde también encontramos tradicionalmente mucha redundancia:

public class MyClass
{
    private Invoice invoice = new Invoice(123);
    private Dictionary<string, Person> people = new Dictionary<string, Person>();
    ...
}

Pues hemos tenido que esperar hasta C# 9 para ver simplificadas estas construcciones, y alguna más que veremos a continuación.

¡Bienvenidas, target-typed new expressions!

Las target-typed new expressions permiten obviar el nombre de la clase al usar su constructor, siempre que el compilador pueda inferir de forma inequívoca el tipo de que se trata. Así pues, podemos escribir el código como el siguiente:

public class MyClass
{
    private Invoice invoice = new (123);
    private Dictionary<string, Person> people = new ();
    ...
}

Como podéis ver, bastaría con utilizar la palabra reservada new() y, en su caso, los parámetros del constructor. No hace falta repetir el tipo de dato, ya que está claramente identificado en la definición del campo, por lo que el resultado es un código menor verboso y, por tanto, más legible.

Por supuesto, esto sería aplicable también a variables locales, por lo que tendríamos ya tres fórmulas distintas para realizar asignaciones a nuevas instancias:

Invoice invoice = new Invoice(123); // C# < 3
var invoice = new Invoice(123);     // C# < 9 
Invoice invoice = new (123);        // C# >= 9

Además de en la declaración de variables o campos, esta capacidad de omitir el tipo de datos al usar el constructor puede utilizarse en todas aquellas expresiones en las que tenga sentido instanciar objetos, como en la invocación de métodos:

// Antes: DoSomething(new Person("Pedro"));
DoSomething(new ("Pedro")); // Ok, el compilador inferir el tipo Person
...
public void DoSomething(Person person) { ... }

Fijaos que el ejemplo anterior es un uso posible de esta nueva característica, aunque, en esta ocasión, viendo la llamada a DoSomething(new ("Pedro")) no podemos intuir de qué tipo de objeto se trata simplemente viendo su constructor. En este caso creo sería más conveniente usar el constructor de forma tradicional.

También podemos usarlo en expresiones throw como la siguiente, donde se asumirá que estamos instanciando un objeto Exception:

throw new("Cachis!");

O cuando aprovechemos la instanciación para inicializar propiedades:

Invoice inv = new() { Id = 1, CustomerId=99, Amount=1000 };
Dictionary<string, Person> people = new()
{
    ["1234"] = new("Pedro"),
    ["5678"] = new("Juan"),
};

En cambio, no será posible su uso en otros escenarios como los siguientes, aunque los motivos son bastante razonables:

dynamic d = new();                // new dynamic() tampoco está permitido
string[] = new();                 // ¿Con qué tamaño se inicializaría?
var x = new { Property = new() }; // No se puede inferir el tipo

En definitiva, como podéis ver, se trata de una interesante característica que utilizada apropiadamente puede aumentar la legibilidad de nuestro código y ahorrarnos muchas pulsaciones de tecla que, como bien nos recuerda a menudo Scott Hanselman, son limitadas ;)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!