1. Introducción
Imagina que tienes una función que, dado un usuario (por ejemplo, por nombre), debe devolverte a la vez la edad, la fecha de registro y un flag de actividad. En C y en las primeras versiones de C# era normal que el tipo de retorno fuera solo un valor. ¿Qué hacer si hay varios valores? Esta pregunta dio vida a diferentes enfoques, cada uno con sus ventajas y desventajas.
En las lecciones anteriores ya vimos las tuplas. Ahora vamos a estudiar los out-parámetros y comparar estos dos enfoques.
Los enfoques más populares:
- Usar out-parámetros.
- Devolver algún objeto con los campos necesarios (tipo anónimo o clase propia).
- Devolver una tupla (ValueTuple).
En esta lección vamos a ver en detalle dos de estos enfoques – los out-parámetros y las tuplas, y los vamos a comparar "cara a cara" para entender las ventajas de cada uno a la hora de devolver varios valores desde una función.
2. out-parámetros: un saludo del pasado
Cuando ves una función tipo:
void GetUserInfo(string userName, out int age, out DateTime registrationDate, out bool isActive)
{
// aquí van los cálculos
age = 42;
registrationDate = new DateTime(2010, 1, 1);
isActive = true;
}
te dan ganas de preguntar: ¿esto es una función o una pequeña sucursal de un lavadero de coches? ¡Devuelve tantas cosas y todo fuera del valor principal de retorno!
Cómo funciona esto
El código que llama a la función debe declarar de antemano las variables que la función va a rellenar:
int age;
DateTime reg;
bool isActive;
GetUserInfo("Bob", out age, out reg, out isActive);
// Ahora todas las variables están rellenas
Console.WriteLine($"{age}, {reg}, {isActive}");
Desventajas del enfoque out
- Más difícil de leer: La signatura del método se hace más ancha, el sentido de los datos devueltos no siempre es claro por el nombre de los parámetros.
- Incómodo en cadenas de llamadas: No puedes meter fácilmente este método en una cadena (por ejemplo, pasar el resultado directamente a otro método).
- Muta las variables pasadas: El método está obligado a modificar variables existentes; si olvidas el out — error de compilación.
- Retraso en la inicialización: El compilador te obliga a declarar variables externas, incluso si solo quieres usar el resultado una vez.
- Legibilidad limitada en métodos grandes: Si hay muchos out-parámetros, es fácil perderse en el orden y el propósito de cada uno.
Aquí es donde las tuplas empiezan a brillar con su aura estelar.
3. Tuplas — la evolución del enfoque
Comparemos con un ejemplo
Con una tupla:
public (int Age, DateTime RegistrationDate, bool IsActive) GetUserInfo(string userName)
{
// simulación de búsqueda en la base de datos
return (42, new DateTime(2010, 1, 1), true);
}
Uso:
// Obtenemos todos los valores a la vez y les damos nombres a las variables
var (age, regDate, isActive) = GetUserInfo("Bob");
Console.WriteLine($"{age}, {regDate}, {isActive}");
¡Sin declaraciones extra, sin out, todo súper legible!
Tabla comparativa: out vs tuple
| Criterio | Parámetros Out | Tuplas (ValueTuple) |
|---|---|---|
| Signatura | larga, out-parámetros en la lista | compacta, todo se devuelve en un solo "paquete" |
| Uso | hay que declarar variables y luego llamar al método | puedes desestructurar directamente |
| Legibilidad | a menudo se pierde si hay varios out-parámetros | los nombres de los elementos se entienden al instante |
| ¿Fácil de combinar? | no | sí, puedes anidar y pasar |
Tuplas VS. out — qué pasa por debajo
Con los out-parámetros la función en realidad trabaja con memoria fuera de su "territorio": simplificando, modifica variables que se crearon en otro sitio. Esto requiere cuidado, porque puedes ensuciar el estado de las variables (¡gracias al compilador por obligar a asignarles valor!).
La tupla, en cambio, es una estructura de datos que la función crea, inicializa completamente y devuelve. Así, todo es un solo paquete que no vas a perder por el camino ni olvidar.
Leer y mantener el código
Admite que — cuando una semana después miras código ajeno, quieres ver:
(var age, var city, var isActive) = GetUserInfo("Anna");
y no
int age;
string city;
bool isActive;
GetUserInfo("Anna", out age, out city, out isActive);
Las tuplas hacen que las signaturas de los métodos sean menos ruidosas y el resultado de usarlas — intuitivo.
4. Situaciones donde las tuplas son especialmente buenas
1. Cuando necesitas devolver un resultado y un mensaje de error
public (bool Success, string ErrorMessage) TryProcess(string data)
{
if (string.IsNullOrEmpty(data))
return (false, "Sin datos");
// procesamiento de datos...
return (true, "");
}
var (ok, error) = TryProcess(input);
if (!ok)
Console.WriteLine($"Error: {error}");
2. Para funciones que devuelven muchos resultados de búsqueda
public (User? FoundUser, int Index) FindUserByName(User[] users, string name)
{
for (int i = 0; i < users.Length; i++)
{
if (users[i].Name == name)
return (users[i], i);
}
return (null, -1);
}
3. Para “pares” de valores: cálculo de mínimo y máximo
public (int min, int max) FindMinMax(int[] numbers)
{
int min = numbers[0], max = numbers[0];
foreach (var n in numbers)
{
if (n < min) min = n;
if (n > max) max = n;
}
return (min, max);
}
var (minValue, maxValue) = FindMinMax(arr);
GO TO FULL VERSION