C# y .NET: Tuplas y cómo devolver más de un objeto como retorno de una función

11/09/2019Artículo original

A lo largo de la vida de un programador, a veces se encuentra con dilemas de diseño a la hora de escribir el código. Sin ir más lejos, seguramente alguna vez te has hecho la pregunta con la que titula este artículo.

Por ejemplo, imagina que estás creando un método que te gustaría que devolviese un bool (verdadero o falso) para indicar si la ejecución ha sido correcta, pero además quieres obtener un valor de retorno como resultado de la ejecución. De este modo podrías utilizar el valor booleano para saber si todo ha ido bien, y en caso de ser así, trabajar con el retorno, algo como esto:

Llamada a un método Si ha ido bien Ejecuto algo con el resultado si ha ido mal Muestro un mensaje

Para conseguir algo como lo anterior, la mayoría de las veces acabaremos con una de estas 2 opciones:

  • Hacer un método que retorne bool y que tenga un parámetro por referencia (ref) o de salida (out)
  • Escribir una clase que agrupe los dos resultados y que sea lo que devuelve la función

Cada opción tiene sus ventajas y sus inconvenientes. Por ejemplo, si utilizas la primera de las estrategias, tienes que tener en cuenta que la variable en el exterior va a cambiar en el mismo momento que cambia dentro de método sin esperar a que termine de ejecutarse el método completo ya que la referencia está fuera el método. Esto puede provocarte un serio problema si estás trabajando con código en paralelo, como se puede observar en este ejemplo:

class Program{ static void Main(string[] args) { int valor = 0; Task.Run(async () => { await Task.Delay(300); Console.WriteLine($"El valor de la variable es {valor}"); }); var resultado = MetodoReferencia(ref valor); Console.WriteLine("Ha terminado el método por referencia"); valor = 0; Task.Run(async () => { await Task.Delay(300); Console.WriteLine($"El valor de la variable es {valor}"); }); resultado = MetodoParametro(out valor); Console.WriteLine("Ha terminado el método por parámetro de salida"); Console.Read(); } static bool MetodoReferencia(ref int salida) { salida++; //Simulamos cierta carga en el método Thread.Sleep(1000); return true; } static bool MetodoParametro(out int salida) { salida = 11; //Simulamos cierta carga en el método Thread.Sleep(1000); return true; }}

En este caso, el resultado que cabría esperar es que la salida por consola fuese que el valor de la variable es 0, que es su valor inicial y el método desde el que se modifica todavía no ha terminado de ejecutarse. Pero precisamente por lo que hemos dicho un poco más arriba, la cruda realidad es que la salida es algo como esto:

  Pylabra. Aplicación para almacenar vocabulario

En cambio, si utilizas una clase o estructura para devolver los datos, desaparece el problema de que las variables cambien en momentos inesperados, pero por contra vas a tener que crear clases/estructuras para cada tipo de retorno que necesites, aunque solo lo vayas a usar una vez, en una única función.

Las tuplas al rescate

Desde la versión 7.0 de C#, se introdujo una nueva característica del lenguaje llamada tupla (tuple en inglés). Si bien es cierto que ya existían antes, en C# 7 se cambiaron para convertirlas en lo que son hoy en día.

Mediante las tuplas podemos conseguir lo mejor de las dos opciones anteriores: no tenemos que definir una clase para que el valor cambie cuando acaba nuestro método.

Para definir una tupla de “n” valores simplemente los envolvemos entre paréntesis y los separamos por comas. Un ejemplo de uso podría ser este:

class Program{ static void Main(string[] args) { var resultado = MetodoTupla(); if (resultado.Resultado) { Console.WriteLine(resultado.Valor); } } static (bool Resultado, int Valor) MetodoTupla() { if (/*Condiciones del código*/) { return (true, 10); } else { return (false, 0); } }}

En este caso la función devuelve una tupla (varios valores a la vez) y por lo tanto se envuelven esos valores de retorno en paréntesis y separados por comas, dándoles además un nombre:

static (bool Resultado, int Valor) MetodoTupla(){ ....}

y se devuelven los resultados con return haciendo lo mismo:

return (true, 10);

También podríamos definir el método sin darle nombre a la salida, así:

static (bool, int) MetodoTupla()

y funcionaría exactamente igual, con la salvedad de que para acceder a sus propiedades tendremos que utilizar los nombres automáticos Item1, Item2, …, Itemn:

class Program{ static void Main(string[] args) { var resultado = MetodoTupla(); if (resultado.Item1) { Console.WriteLine(resultado.Item2); } } static (bool, int) MetodoTupla() { if (/*Condiciones del código*/) { return (true, 10); } else { return (false, 0); } }}

Y por último, también tenemos la posibilidad de utilizar variables ya existentes que “colocamos en la tupla” para asignar la salida:

class Program{ static void Main(string[] args) { var resultado = false; var valor = 0; (resultado, valor) = MetodoTupla(); if (resultado) { Console.WriteLine(valor); } } static (bool, int) MetodoTupla() { if (/*Condiciones del código*/) { return (true, 10); } else { return (false, 0); } }}

A esta última operación se la conoce con el nombre de deconstrucción de tuplas. De hecho podemos también obviar alguno de los valores devueltos si no lo necesitamos gracias al operador guión bajo.

  Cómo personalizar los estilos CSS de las barras de scroll

La parte mala de las tuplas, es que no tienen compatibilidad hacia atrás, y solo están disponibles de manera nativa desde .Net Framework 4.7 y .Net Core 2.0 en adelante (.Net Standard lo dispone desde la versión 1.0), y con versiones del lenguaje C# de la 7.0 en adelante.

  Estos han sido los fragmentos de código más 'copypasteados' en Stack Overflow durante las últimas semanas

De todos modos, si nuestro proyecto es .Net Framework 4.5 o superior, o es .Net Core, existen un paquete Nuget que nos permite poder utilizar las tuplas sin ningún problema. Para referencia te dejo esta tabla de compatibilidad:

Soporte.Net Framework.Net Core.Net StandardNativo>= 4.7.0>= 2.0>= 1.0Nuget>= 4.5>= 1.0>= 1.0

En resumen

Aunque siempre una clase a medida puede ser más legible si la puedes reutilizar en varios métodos, si vas a tener que definir varias clases que no se van a utilizar más que en un solo sitio, estarías añadiendo mucho ruido al proyecto y dificultando su lectura. Ahí es en donde entran en juego las tuplas ya que te vas a ahorrar tiempo codificando clases que solo vas a usar una o dos veces como retorno de métodos, y encima facilitas su lectura.

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad