CodeGym /Cursos /C# SELF /Devolver varios valores desde una función: parámetros out...

Devolver varios valores desde una función: parámetros out y tuplas

C# SELF
Nivel 11 , Lección 3
Disponible

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);
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION