Na minha opinião, uma tupla é um atalho para escrever uma classe de resultado (tenho certeza de que há outros usos também).
De fato, existem outros usos valiosos paraTuple<>
- a maioria deles envolve abstrair a semântica de um grupo específico de tipos que compartilham uma estrutura semelhante e tratá-los simplesmente como um conjunto ordenado de valores. Em todos os casos, um benefício das tuplas é que elas evitam sobrecarregar seu espaço de nomes com classes somente de dados que expõem propriedades, mas não métodos.
Aqui está um exemplo de uso razoável para Tuple<>
:
var opponents = new Tuple<Player,Player>( playerBob, playerSam );
No exemplo acima, queremos representar um par de oponentes, uma tupla é uma maneira conveniente de emparelhar essas instâncias sem precisar criar uma nova classe. Aqui está outro exemplo:
var pokerHand = Tuple.Create( card1, card2, card3, card4, card5 );
Uma mão de pôquer pode ser considerada apenas um conjunto de cartas - e a tupla (pode ser) uma maneira razoável de expressar esse conceito.
deixando de lado a possibilidade de que estou perdendo o ponto das tuplas, o exemplo de uma tupla é uma má escolha de design?
Retornar Tuple<>
instâncias fortemente tipadas como parte de uma API pública para um tipo público raramente é uma boa idéia. Como você mesmo reconhece, as tuplas exigem que as partes envolvidas (autor da biblioteca, usuário da biblioteca) concordem com antecedência sobre o objetivo e a interpretação dos tipos de tupla que estão sendo usados. É bastante desafiador criar APIs intuitivas e claras, o uso Tuple<>
público apenas obscurece a intenção e o comportamento da API.
Tipos anônimos também são uma espécie de tupla - no entanto, são fortemente tipados e permitem especificar nomes claros e informativos para as propriedades pertencentes ao tipo. Mas tipos anônimos são difíceis de usar em diferentes métodos - eles foram adicionados principalmente para dar suporte a tecnologias como LINQ, em que as projeções produziriam tipos aos quais normalmente não queremos atribuir nomes. (Sim, eu sei que tipos anônimos com os mesmos tipos e propriedades nomeadas são consolidados pelo compilador).
Minha regra geral é: se você o devolverá da sua interface pública - torne-o um tipo nomeado .
Minha outra regra prática para usar tuplas é: argumentos do método name e variáveis localc do tipo o Tuple<>
mais claramente possível - faça o nome representar o significado dos relacionamentos entre os elementos da tupla. Pense no meu var opponents = ...
exemplo.
Aqui está um exemplo de um caso do mundo real em que eu costumava Tuple<>
evitar declarar um tipo somente de dados para uso somente dentro do meu próprio assembly . A situação envolve o fato de que, ao usar dicionários genéricos contendo tipos anônimos, torna-se difícil usar o TryGetValue()
método para localizar itens no dicionário porque o método requer um out
parâmetro que não pode ser nomeado:
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 );
}
}
}
PS Existe outra maneira (mais simples) de contornar os problemas decorrentes de tipos anônimos nos dicionários, e é usar a var
palavra-chave para permitir que o compilador 'deduza' o tipo para você. Aqui está essa versão:
var foundItem = peopleDict.FirstOrDefault().Value;
if( peopleDict.TryGetValue( "Smith", out foundItem ) )
{
// use foundItem...
}