A mi modo de ver, una Tupla es un atajo para escribir una clase de resultado (estoy seguro de que también hay otros usos).
De hecho, existen otros usos valiososTuple<>
: la mayoría de ellos implican abstraer la semántica de un grupo particular de tipos que comparten una estructura similar y tratarlos simplemente como un conjunto ordenado de valores. En todos los casos, un beneficio de las tuplas es que evitan abarrotar su espacio de nombres con clases de solo datos que exponen propiedades pero no métodos.
Aquí hay un ejemplo de uso razonable para Tuple<>
:
var opponents = new Tuple<Player,Player>( playerBob, playerSam );
En el ejemplo anterior, queremos representar un par de oponentes, una tupla es una forma conveniente de emparejar estas instancias sin tener que crear una nueva clase. Aquí hay otro ejemplo:
var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
Una mano de póker puede considerarse como un juego de cartas, y la tupla (puede ser) una forma razonable de expresar ese concepto.
Dejando a un lado la posibilidad de que me estoy perdiendo el punto de Tuples, ¿es el ejemplo con una Tuple una mala elección de diseño?
Devolver Tuple<>
instancias fuertemente tipadas como parte de una API pública para un tipo público rara vez es una buena idea. Como usted mismo reconoce, las tuplas requieren que las partes involucradas (autor de la biblioteca, usuario de la biblioteca) acuerden de antemano el propósito y la interpretación de los tipos de tuplas que se utilizan. Es lo suficientemente desafiante crear API que sean intuitivas y claras, su uso Tuple<>
público solo oscurece la intención y el comportamiento de la API.
Los tipos anónimos también son una especie de tupla ; sin embargo, están fuertemente tipados y le permiten especificar nombres claros e informativos para las propiedades que pertenecen al tipo. Pero los tipos anónimos son difíciles de usar en diferentes métodos: se agregaron principalmente para admitir tecnologías como LINQ, donde las proyecciones producirían tipos a los que normalmente no quisiéramos asignar nombres. (Sí, sé que el compilador consolida los tipos anónimos con los mismos tipos y propiedades con nombre).
Mi regla general es: si lo devolverá desde su interfaz pública, hágalo como un tipo con nombre .
Mi otra regla general para usar tuplas es: nombrar los argumentos del método y las variables de tipo localc con la Tuple<>
mayor claridad posible: hacer que el nombre represente el significado de las relaciones entre los elementos de la tupla. Piensa en mi var opponents = ...
ejemplo.
Aquí hay un ejemplo de un caso del mundo real en el que solía Tuple<>
evitar declarar un tipo de solo datos para usar solo dentro de mi propio ensamblado . La situación implica el hecho de que cuando se usan diccionarios genéricos que contienen tipos anónimos, se hace difícil usar el TryGetValue()
método para encontrar elementos en el diccionario porque el método requiere un out
parámetro que no se puede nombrar:
public static class DictionaryExt
{
// helper method that allows compiler to provide type inference
// when attempting to locate optionally existent items in a dictionary
public static Tuple<TValue,bool> Find<TKey,TValue>(
this IDictionary<TKey,TValue> dict, TKey keyToFind )
{
TValue foundValue = default(TValue);
bool wasFound = dict.TryGetValue( keyToFind, out foundValue );
return Tuple.Create( foundValue, wasFound );
}
}
public class Program
{
public static void Main()
{
var people = new[] { new { LastName = "Smith", FirstName = "Joe" },
new { LastName = "Sanders", FirstName = "Bob" } };
var peopleDict = people.ToDictionary( d => d.LastName );
// ??? foundItem <= what type would you put here?
// peopleDict.TryGetValue( "Smith", out ??? );
// so instead, we use our Find() extension:
var result = peopleDict.Find( "Smith" );
if( result.First )
{
Console.WriteLine( result.Second );
}
}
}
PD: Hay otra forma (más simple) de solucionar los problemas que surgen de los tipos anónimos en los diccionarios, y es usar la var
palabra clave para permitir que el compilador 'infiera' el tipo por usted. Aquí está esa versión:
var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
// use foundItem...
}