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, 13 de junio de 2017
C#Y continuamos escarbando en las nuevas características disponibles en C#7, incluido de serie en Visual Studio 2017. Hasta el momento hemos profundizado en las siguientes novedades:
En esta ocasión nos centraremos en la vuelta de tuerca que se ha dado a las tuplas a nivel de lenguaje, reforzándolas como first-class citizens para los desarrolladores C#.

Pero empecemos desde el principio…

Como recordaréis, la clase Tuple está con nosotros desde .NET 4 y podíamos utilizarla como un "cajón" en el que almacenar una serie de valores relacionados entre sí sin necesidad de crear una clase o estructura específica para cada ocasión. Gracias a los tipos incluidos en el framework Tuple<T>, Tuple<T1, T2>Tuple<T1, T2, T3, T4, T5, T6, T7> , podemos crear tuplas de hasta siete elementos con suma facilidad, por ejemplo:
// Tupla con dos elementos enteros:
var coords = new Tuple<int,int>(100, 200);
// Creación usando factoría con inferencia de tipos:
var coords = Tuple.Create(100, 200);
// Con tres elementos:
var coords3d = Tuple.Create(100, 200, 300); 
// Con cuatro elementos de distinto tipo:
var person = Tuple.Create("John", new DateTime(1997, 2, 3), 1.68m, true);
Cada tupla tenía propiedades como Item1 para acceder al primer elemento, Item2 para el segundo, y así sucesivamente hasta llegar al número de elementos de la misma. Esto hacía que el acceso a estas propiedades fuera sencillo, aunque a nivel de legibilidad de código dejara bastante que desear:
Console.WriteLine(coords.Item2); // 200
Console.WriteLine(coords3d.Item3); // 300
Console.WriteLine(person.Item4); // True
Sin embargo, es cierto que las tuplas tienen sentido en algunos escenarios, como cuando queremos que un método retorne más de un valor. Muchas veces podemos usar parámetros out (que, como sabéis, también han sido mejorados en C# 7), pero por ejemplo no están disponibles en métodos asíncronos. También son útiles para evitar la creación de clases de transferencia de datos sólo para determinados métodos, o incluso para evitar el uso de tipos dinámicos, objetos anónimos, diccionarios u otras fórmulas de almacenamiento de datos.

Introducing ValueTuple

Pues bien, lo que han hecho en C#7 es un reboot de la idea. Han creado un tipo nuevo llamado ValueTuple para representar las tuplas, y han añadido al lenguaje azúcar sintáctico para gestionarlo de forma más cómoda.

A diferencia del tipo Tuple clásico el nuevo ValueTuple es un struct, por lo que es un tipo valor, más eficiente en términos de uso de memoria (se almacenan en la pila, nada de allocations, presión en recolección de basura y esas hierbas), y hereda características como las operaciones de igualdad o la obtención del hash code. En este issue de Github podéis profundizar en los motivos que llevaron a tomar esta decisión.

Salvo esas pequeñas diferencias, su uso a priori es muy parecido a Tuple:
var coords3d = ValueTuple.Create(100, 200, 300);
Console.WriteLine(coords3d.Item1); // 100
Ah, y ojo, que dado que el tipo ValueTuple no ha sido añadido al framework hasta la reciente versión 4.7, para usar las tuplas de C#7 en vuestros proyectos .NET 4.6 y anteriores debéis instalar el paquete Nuget "System.ValueTuple".

Uso de tuplas desde C# 7

Bien, veamos a nivel de código cómo podemos utilizar este nuevo tipo de datos aprovechando su integración con C#7. En primer lugar, observad lo sencillo que resulta ahora crear una tupla con la nueva sintaxis; simplemente debemos especificar los elementos entre paréntesis, separados por una coma:
var person = ("John", 34);
Console.WriteLine(person.Item1);  // John
Console.WriteLine(person.Item2);  // 34
El código anterior crearía un ValueTuple<string, int>, con los valores ya establecidos. ¿Fácil, eh? Pero aunque hemos mejorado la forma de crearla, aún el acceso a los elementos de la tupla lo hacemos utilizando sus miembros Item1 e Item2, algo totalmente inapropiado para los tiempos que corren (¿Qué es Item1? ¿El nombre de la persona o el de su perro? ¿Y 34? ¿Su talla de zapatos?).

Por esta razón, C# nos permite endulzar un poco esta situación facilitando la asignación de nombres a las propiedades. Fijaos ahora cómo mejora la cosa:
var person = (name: "John", age: 34);
Console.WriteLine(person.name); // John
Console.WriteLine(person.age);  // 34
Console.WriteLine(
   person.GetType() // System.ValueTuple`2[System.String,System.Int32]
);
Como podéis observar, se trata de una sintaxis muy compacta, y en cierto sentido bastante parecida a los tipos anónimos. Sin embargo, fijaos que sigue siendo una tupla, sólo que el compilador tiene la cortesía de permitirnos el acceso a sus miembros vía propiedades nombradas en lugar de Item1 o Item2. De hecho, si compilamos el código anterior y descompilamos el código resultante, encontraríamos algo como esto:
ValueTuple<string, int> valueTuple = new ValueTuple<string, int>("John", 34);
Console.WriteLine(valueTuple.Item1);
Console.WriteLine(valueTuple.Item2);
Este tipo de tuplas son ciudadanos de primer orden en C#, por lo que pueden pasarse como argumentos y retornarse desde métodos con total normalidad. Por ejemplo, en el siguiente código vemos cómo podríamos declarar parámetros de un tipo tupla:
var person = (name: "John", age: 34);
Show(person);
...
static void Show((string name, int age) person)
{
   Console.WriteLine($"Name: {person.name}");
   Console.WriteLine($"Age: {person.age}");
}
A continuación vemos también cómo consumir un método que retorna una tupla con miembros nombrados:
var person = Create();
Console.WriteLine(person.age); // 23
...

static (string name, int age) Create()
{
   return (name: "Test", age: 23);
}

Deconstrucción de tuplas

Otra forma curiosa de consumir estos tipos es mediante la deconstrucción. Aunque es un tema que da para otro post, la deconstrucción consiste en “despiezar” un objeto o estructura y crear variables locales con las propiedades o miembros que nos interesen. Por ejemplo, el siguiente código muestra cómo deconstruir una tupla para obtener los valores como variables independientes:
(var name, var age, var height) = Create();
//O bien, más conciso: var (name, age, height) = Create();
Console.WriteLine($"{name} is {age} years old");
...

static (string name, int age, decimal height) Create()
{
   return (name: "John", age: 23, height: 1.68m);
}
También podríamos haber especificado los tipos concretos para cada variable al hacer la deconstrucción, pero la inferencia hace posible el uso de “var”, por lo que es más cómodo a la hora de codificar.

Esto de la deconstrucción tiene más chicha porque se trata de un mecanismo "configurable", así que probablemente más adelante le dediquemos un post en exclusiva. Pero mientras tanto, podéis echar un vistazo aquí.

En conclusión…

En resumen, estamos ante una interesante característica de C# 7 que bien utilizada puede ayudarnos a hacer nuestro código más limpio y expresivo al facilitar soporte de primer nivel para tuplas. Podemos crear tuplas con una sintaxis muy sencilla, dar nombres a sus elementos, pasarlas como argumentos o retornarlas desde métodos o funciones, dispondremos de comprobaciones en tiempo de compilación, intellisense… en definitiva, todo lo que podríamos esperar de un ciudadano de primera clase en el lenguaje. Además, su implementación interna deja a un lado la antigua clase Tuple y se apoya en la nueva estructura ValueTuple, lo que trae consigo mejoras en rendimiento y aprovechamiento de memoria.

Pero ojo, que como casi siempre ocurre, no se trata de una bala de plata. Personalmente, se me antoja raro el hecho de que puedan desarrollarse aplicaciones completas donde el paso de información entre capas o componentes se base exclusivamente en tuplas, sin necesidad de crear clases o estructuras específicas para cada caso.

De hecho, los diseñadores de C#7 lo han hecho tan sencillo que será difícil esquivar la pereza de crear clases en escenarios que fácilmente podamos solucionar con una tupla. Como siempre, es una característica que debe usarse con sentido común y no a destajo con el simple argumento de que es más rápido de escribir, sino tener en cuenta otros aspectos como la legibilidad del código o las posibilidades de reutilización a la hora de optar por este enfoque. Sin duda, es mucho más claro y conveniente que un método GetInvoice() retorne un objeto Invoice a que retorne una tupla de N elementos, uno por cada propiedad de la factura.

Habrá que ver, ya aplicado en la práctica, cómo vamos utilizando estos nuevos superpoderes que nos han sido concedidos ;)

Pero eso sí, son mecanismos útiles, y sin duda recomendables, cuando nos interese que un método o función retorne varios valores sin usar parámetros out, para agrupar valores con propiedades de igualdad (por ejemplo, para crear claves compuestas en diccionarios o almacenar varios valores en el interior de colecciones o listas), o cuando estemos ante un escenario donde la optimización milimétrica del rendimiento sea imprescindible.

Publicado en Variable not found.

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