1. Nombrar tuplas: contexto para la deconstrucción
En la lección anterior ya vimos los elementos nombrados de las tuplas, que permiten que el código sea mucho más legible, accediendo a los valores no por los impersonales Item1, Item2, sino por nombres con sentido (por ejemplo, person.Name). Esta posibilidad de poner nombres a los elementos de la tupla es especialmente importante cuando se usan como valores de retorno de métodos, parámetros o propiedades. Justo el nombrado es la clave para una deconstrucción cómoda, que vamos a ver más adelante en esta lección.
Recuerda, los nombres se pueden poner al inicializar la tupla con un literal, y también en la firma del valor de retorno de un método, propiedad o campo.
Ejemplo con método: Elementos nombrados de tupla en el valor de retorno de un método
public static (int Edad, string Nombre) GetPetInfo()
{
return (Edad: 5, Nombre: "Fluffy");
}
// Uso:
var info = GetPetInfo();
Console.WriteLine($"{info.Nombre} — {info.Edad} años");
Ejemplo con campo/propiedad: Elementos nombrados de tupla en un campo/propiedad
public (int Ancho, int Alto) ImageSize = (1024, 768);
Recuerda: si no pones nombres explícitamente, los elementos seguirán estando accesibles por Item1, Item2 y así. Esto puede hacer el código menos legible, sobre todo si usas la tupla más adelante. Por eso se recomienda siempre poner nombres con sentido a los elementos, si su significado no es obvio por el contexto.
2. Deconstrucción de tuplas
¿Qué es la deconstrucción?
La deconstrucción es el proceso de "desempaquetar" una tupla en variables separadas, para trabajar con ellas de forma cómoda. O sea, puedes sacar de la tupla (Edad: 5, Nombre: "Fluffy") dos variables: edad y nombre.
Analogía: Imagina que la tupla es una caja con compartimentos etiquetados. Deconstruir es como sacar todo de la caja y ponerlo en su sitio en la mesa.
Sintaxis de deconstrucción
var pet = (Edad: 5, Nombre: "Fluffy");
var (edad, nombre) = pet;
Console.WriteLine($"{nombre} — {edad} años");
Ahora tenemos dos variables: edad y nombre. Reciben los valores de la tupla. Los nombres a la izquierda (edad, nombre) no tienen que coincidir con los de la tupla, son simplemente nuevas variables locales.
Deconstrucción del valor de retorno de una función
Muchas veces las tuplas se usan para devolver varios valores de una función. Ahí la deconstrucción es especialmente cómoda:
public static (double minimo, double maximo) GetMinMax(int[] datos)
{
int minimo = datos.Min();
int maximo = datos.Max();
return (minimo, maximo);
}
var numeros = new[] { 1, 2, 3, 4, 5 };
var (valorMinimo, valorMaximo) = GetMinMax(numeros);
Console.WriteLine($"Mínimo: {valorMinimo}, máximo: {valorMaximo}");
Fíjate que en la deconstrucción puedes llamar a las variables como te venga mejor en ese contexto.
Deconstrucción con var
var (a, b) = (10, 20); // int a = 10, b = 20
Deconstrucción y discard _
A veces no necesitas todos los elementos de la tupla. Puedes ignorarlos usando _ (discard). El discard (_) en la tupla es solo una forma de decir "sé que aquí hay otro elemento, pero no lo quiero, no crees una variable para él".
Así, deconstruyes la tupla pero dejas un "hueco" donde no necesitas el valor. ¡Corto, cómodo y sin complicaciones!
var pet = (Edad: 5, Nombre: "Fluffy", EsFeliz: true);
var (edad, _, esFeliz) = pet; // solo edad y esFeliz, nombre ignorado
Dato curioso: el discard se usa mucho para no llenar el espacio de nombres con variables que no necesitas.
Deconstrucción en el bucle foreach
En C# puedes recorrer un array de tuplas deconstruyendo directamente en el propio foreach:
var mascotas = new (string Nombre, int Edad)[]
{
("Fluffy", 5),
("Luna", 3),
("Johnny", 7)
};
foreach (var (nombre, edad) in mascotas)
{
Console.WriteLine($"{nombre} — {edad} años");
}
3. Cómo funcionan los nombres de los elementos y el tipado
Comportamiento de los elementos nombrados
Los nombres de los elementos de la tupla son "azúcar sintáctico", o sea, solo para humanos. Al compilar, los nombres se convierten en campos internos Item1, Item2, etc., pero el IDE y el compilador guardan los nombres para que los puedas usar.
Influencia en la compatibilidad de tipos y casting
Dos tuplas con el mismo número de elementos y tipos, pero con nombres diferentes, se consideran del mismo tipo para el compilador. Los nombres de los elementos no forman parte de la definición del tipo:
var t1 = (X: 42, Y: 13);
var t2 = (A: 42, B: 13);
t1 = t2; // OK
Console.WriteLine(t1.X); // 42
Pero al trabajar con expresiones y sugerencias de IntelliSense, se usarán los nombres de la parte izquierda (donde asignas), no los de la derecha.
Nombrado implícito y explícito
Ya lo vimos, pero por si acaso: puedes crear tuplas con algunos elementos nombrados, o sin nombrar ninguno — entonces serán simplemente Item1 y así.
var punto = (X: 10, 20); // X y Item2
Console.WriteLine(punto.X); // 10
Console.WriteLine(punto.Item2); // 20
Consejo: nombra siempre los elementos si la tupla tiene más de uno o dos elementos, o si el significado del valor no es claro por el contexto.
4. Errores típicos y particularidades al trabajar con nombres y deconstrucción
Error nº1: “traslado” de nombres al asignar diferentes tuplas
Si primero declaras una tupla con unos nombres, y luego le asignas otra tupla sin nombres (o con otros nombres), en IntelliSense seguirán apareciendo los nombres originales. Por ejemplo:
var original = (X: 1, Y: 2);
var alias = original; // alias.X == 1, alias.Y == 2
original = (10, 20); // tupla sin nombres
Console.WriteLine(alias.X); // sigue funcionando, pero alias sigue guardando los nombres antiguos
Error nº2: duplicar nombres de elementos.
No puedes poner el mismo nombre a dos elementos — el compilador dará el mensaje “Duplicate tuple element name”:
var tuplaMala = (A: 1, A: 2); // Error CS8122: Duplicate tuple element name 'A'
Error nº3: deconstrucción incorrecta por número de elementos.
Al deconstruir, el número de variables debe coincidir exactamente con el tamaño de la tupla. Si no, da error:
var pet = (Edad: 5, Nombre: "Fluffy");
var (edad, nombre, humor) = pet; // Error CS8124: Tuple must contain exactly 3 elements
Error nº4: mal uso de discard _
Ignorar elementos con _ funciona para cada sitio por separado y no “une” los huecos en uno solo. Por ejemplo, si intentas saltarte dos elementos con un solo _, te dará error:
var datos = (1, 2, 3);
var (_, x, _) = datos; // Correcto: saltados primer y tercer elemento
var (_, _) = datos; // Error CS8124: Tuple must contain exactly 2 elements
GO TO FULL VERSION