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, 27 de junio de 2017
C#Hace poco hablábamos del nuevo soporte para tuplas de C#, y comentábamos que una forma de consumirlas era mediante la deconstrucción, otra nueva característica introducida en la versión 7 del lenguaje, que consiste en “despiezar” las tuplas, extrayendo de ellas sus elementos e introduciéndolos en variables locales, utilizando una sintaxis muy concisa.

En realidad no es algo demasiado diferente a lo que hacemos normalmente cuando almacenamos en una variable local el resultado de un método que retorna un único valor:
// Guardamos el valor de retorno en variable local
var sum = Sum(1, 3); 
Console.WriteLine($"Sum: {sum}");
...

static int Sum(int a, int b)
{
   return a+b;
}

Pues bien, cuando un método retorna más de un valor en forma de tupla, podemos conseguir exactamente lo mismo para todos los valores retornados, de una forma igualmente sencilla. El siguiente ejemplo muestra un método que retorna una tupla, y cómo la deconstruimos para obtener sus valores en forma de nuevas variables locales:
// Guardamos los valores de retorno en variables locales
var (add, sub, mul, div) = Calculate(1, 3); 
Console.WriteLine($"Sum: {add}");
...

static (int add, int sub, int mul, int div) Calculate(int a, int b)
{
    return (a + b, a - b, a * b, a / b);
}
Observad que hemos utilizado la palabra clave var para indicar al compilador que sea él el que se encargue de inferir los tipos de los elementos de la tupla. Pero si nos va el masoquismo podríamos hacerlo de forma mucho más trabajosa; las siguientes líneas son equivalentes:
(int add, int sub, int mul, int div) = Calculate(1, 3);
(var add, var sub, var mul, var div) = Calculate(1, 3);
var (add, sub, mul, div) = Calculate(1, 3);
También podemos deconstruir sobre variables existentes con anterioridad, como en el siguiente ejemplo:
int add, sub, mul, div;
...
(add, sub, mul, div) = Calculate(1, 3);
Console.WriteLine($"Sum: {add}");
Por supuesto, los nombres de las variables locales pueden ser diferentes a los nombres asignados a los miembros de la tupla. Lo que importa es el orden, que sí debe coincidir (¡recordad que una tupla es una lista ordenada de elementos!):
var (a, s, m, d) = Calculate(1, 3);
Console.WriteLine($"Sum: {a}");
Otro aspecto interesante es que si, como en el ejemplo anterior, no vamos a utilizar todos los elementos de la tupla, podemos sustituir por el placeholder "_" los elementos que no nos interesen:
var (add, _, _, div) = Calculate(1, 3);
Console.WriteLine($"Sum: {add}, Div: {div}");

La deconstrucción más allá de las tuplas

Aunque hemos visto la deconstrucción en el ámbito de las tuplas, donde tienen una aplicación sencilla y directa, en realidad es una característica del lenguaje que puede utilizarse con cualquier tipo de .NET. No veo que sea algo excesivamente útil en la práctica, e incluso dudo mucho que favorezca la legibilidad del código, pero bueno, siempre es bueno saber que existe esta posibilidad y cómo implementarla.

Para que un tipo cualquiera (clase o estructura) pueda ser deconstruido, debe implementar un método void llamado Deconstruct(), más o menos con la siguiente pinta:
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }

    public void Deconstruct(out string name, out int age, out string email)
    {
        name = Name;
        age = Age;
        email = Email;
    }
}
El número y tipo de parámetros out en Deconstruct() es el que define las "piezas" en las que podrán ser deconstruidos los objetos de nuestra clase. Así, según el código anterior, podríamos efectuar una deconstrucción de la siguiente manera:
var person = _myServices.GetPersonById(3422);
...
var (name, age, email) = person;
Console.WriteLine($"I'm {name}, {age} years old");
Console.WriteLine($"Contact me at {email}");
De hecho, si descompilamos un código como el visto anteriormente, descubriremos que, al fin y al cabo, la deconstrucción es un simple edulcorante sintáctico sobre las llamadas al método Deconstruct():
// Decompiled sources:
string name;
string email;
int age;
person.Deconstruct(out name, out age, out email);
Por este motivo, en la práctica es posible crear distintas sobrecargas de Deconstruct() que serán invocadas en función del número de parámetros que utilicemos en la deconstrucción:
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }

    // Consumo: var (name, age) = person;
    public void Deconstruct(out string name, out int age)
    {
        name = Name;
        age = Age;
    }

    // Consumo: var (name, age, email) = person;
    public void Deconstruct(out string name, out int age, out string email)
    {
        name = Name;
        age = Age;
        email = Email;
    }
}
En definitiva, tenemos delante una vuelta de tuerca al lenguaje que permite sacar mayor partido a las tuplas y facilita la codificación en algunos escenarios. Aunque probablemente no vayamos a usarla todos los días, sin duda es una herramienta interesante para tener en nuestro cinturón de herramientas.

Por último, por si os interesa, os recuerdo otros posts de esta serie sobre novedades de C# 7:
Publicado en Variable not found.

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