Com a adição da classe Tuple no .net 4, tenho tentado decidir se usá-los em meu design é uma má escolha ou não. Do meu ponto de vista, uma tupla pode ser um atalho para escrever uma classe de resultado (tenho certeza de que há outros usos também).
Então, é isso:
public class ResultType
{
public string StringValue { get; set; }
public int IntValue { get; set; }
}
public ResultType GetAClassedValue()
{
//..Do Some Stuff
ResultType result = new ResultType { StringValue = "A String", IntValue = 2 };
return result;
}
É equivalente a isso:
public Tuple<string, int> GetATupledValue()
{
//...Do Some stuff
Tuple<string, int> result = new Tuple<string, int>("A String", 2);
return result;
}
Então, deixando de lado a possibilidade de eu estar perdendo o ponto das Tuplas, o exemplo de uma Tupla é uma má escolha de design? Para mim, parece menos confuso, mas não tão auto-documentável e limpo. Isso significa que, com o tipo ResultType
, fica muito claro mais tarde o que cada parte da classe significa, mas você tem um código extra para manter. Com o, Tuple<string, int>
você precisará procurar e descobrir o que cada um Item
representa, mas você escreve e mantém menos código.
Qualquer experiência que você teve com essa escolha seria muito apreciada.
fonte
Respostas:
As tuplas são ótimas se você controla a criação e o uso delas - é possível manter o contexto, essencial para entendê-las.
Em uma API pública, no entanto, eles são menos eficazes. O consumidor (não você) precisa adivinhar ou procurar documentação, especialmente para coisas como
Tuple<int, int>
.Eu os usaria para membros privados / internos, mas usaria classes de resultado para membros públicos / protegidos.
Esta resposta também tem algumas informações.
fonte
StringOps
(basicamente uma classe de "métodos de extensão" para JavaString
)) possui um métodoparition(Char => Boolean): (String, String)
(pega no predicado, retorna uma tupla de 2 strings). É óbvio qual é a saída (obviamente, um será os caracteres para os quais o predicado era verdadeiro e o outro para o qual era falso, mas não está claro qual é qual). É a documentação que esclarece isso (e a correspondência de padrões pode ser usada para deixar claro no uso qual é qual).De fato, existem outros usos valiosos para
Tuple<>
- 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<>
: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:
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.
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 usoTuple<>
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 meuvar 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 oTryGetValue()
método para localizar itens no dicionário porque o método requer umout
parâmetro que não pode ser nomeado: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:fonte
Tuple<>
poderia fazer sentido. Há sempre alternativas ...Tuplas podem ser úteis ... mas também podem ser uma dor depois. Se você possui um método que retorna
Tuple<int,string,string,int>
como você sabe quais são esses valores posteriormente. ElesID, FirstName, LastName, Age
eram ou eram elesUnitNumber, Street, City, ZipCode
.fonte
As tuplas são uma adição bastante abaixo do esperado para o CLR da perspectiva de um programador de C #. Se você possui uma coleção de itens que variam em tamanho, não é necessário que eles tenham nomes estáticos exclusivos no momento da compilação.
Mas se você tiver uma coleção de comprimento constante, isso implica que cada local fixo na coleção possui um significado predefinido específico. E é sempre melhor dar-lhes nomes estáticos apropriados, nesse caso, ao invés de ter que lembrar o significado
Item1
,Item2
etc.As classes anônimas em C # já fornecem uma excelente solução para o uso privado mais comum de tuplas, e elas dão nomes significativos aos itens, portanto são realmente superiores nesse sentido. O único problema é que eles não podem vazar dos métodos nomeados. Eu preferiria ver essa restrição levantada (talvez apenas para métodos privados) do que ter suporte específico para tuplas em C #:
fonte
struct var SimpleStruct {int x, int y;}
, definir uma estrutura com um nome que comece com um guia associado a estruturas simples e tem algo como{System.Int16 x}{System.Int16 y}
anexado; qualquer nome começando com esse GUID seria necessário para representar uma estrutura contendo apenas esses elementos e conteúdo específico especificado; se várias definições para o mesmo nome estiverem no escopo e corresponderem, todas serão consideradas do mesmo tipo.ref
parâmetro, a única maneira possível de ter um delegado que pode ser passado para ambas as funções será se a pessoa produzir e compilar um tipo de delegado e dá para o outro construir contra. Não há como as duas pessoas indicarem que os dois tipos devem ser considerados sinônimos.Tuple
? Acabei de percorrer 50 lugares da minha base de código onde estou usando tuplas, e todos são valores de retorno (normalmenteyield return
) ou são elementos de coleções que são usadas em outros lugares, portanto, não vá para classes anônimas.Semelhante à palavra-chave
var
, destina-se como uma conveniência - mas é facilmente abusada.Na minha humilde opinião, não exponha
Tuple
como uma classe de retorno. Use-o em particular, se a estrutura de dados de um serviço ou componente exigir, mas retorne classes conhecidas e bem formadas a partir de métodos públicos.fonte
var
não pode ser devolvido ou existir fora do escopo declarado. no entanto, ainda é possível usá-lo em excesso de sua finalidade e ser "abusado"Usar uma classe como
ResultType
é mais clara. Você pode dar nomes significativos aos campos da classe (enquanto que com uma tupla eles seriam chamadosItem1
eItem2
). Isso é ainda mais importante se os tipos dos dois campos forem iguais: o nome distingue claramente entre eles.fonte
Que tal usar Tuplas em um padrão de decorar, classificar ou não decoração? (Transformação Schwartziana para o povo Perl). Aqui está um exemplo artificial, com certeza, mas as Tuplas parecem ser uma boa maneira de lidar com esse tipo de coisa:
Agora, eu poderia ter usado um Object [] de dois elementos ou, neste exemplo específico, uma string [] de dois elementos. O ponto é que eu poderia ter usado qualquer coisa como o segundo elemento em uma tupla usada internamente e é muito fácil de ler.
fonte
Na OMI, essas "tuplas" são basicamente todos os
struct
tipos anônimos de acesso público com membros sem nome .O único lugar que eu usaria a tupla é quando você precisa reunir rapidamente alguns dados, em um escopo muito limitado. A semântica dos dados deve ser óbvia , portanto, o código não é difícil de ler. Portanto, usar uma tupla (
int
,int
) para (linha, col) parece razoável. Mas tenho dificuldade em encontrar uma vantagem sobre umstruct
membro nomeado (para que nenhum erro seja cometido e a linha / coluna não seja acidentalmente trocada)Se você estiver enviando dados de volta para o chamador ou aceitando dados de um chamador, realmente deve usar um
struct
com membros nomeados.Veja um exemplo simples:
A versão da tupla
Não vejo nenhuma vantagem em usar tupla no lugar de uma estrutura com membros nomeados. O uso de membros sem nome é um passo atrás para a legibilidade e a compreensão do seu código.
Tuple me parece uma maneira preguiçosa de evitar a criação de um
struct
membro com nome real. O uso excessivo de tupla, onde você realmente sente que você / ou alguém que encontra seu código precisaria de membros nomeados é A Bad Thing ™, se eu já vi um.fonte
Não me julgue, eu não sou especialista, mas com as novas Tuplas no C # 7.x agora, você pode retornar algo como:
Pelo menos agora você pode nomear e os desenvolvedores podem ver algumas informações.
fonte
Depende, é claro! Como você disse, uma tupla pode economizar seu código e tempo quando você deseja agrupar alguns itens para consumo local. Você também pode usá-los para criar algoritmos de processamento mais genéricos do que se passar uma classe concreta. Não me lembro quantas vezes desejei ter algo além do KeyValuePair ou DataRow para passar rapidamente uma data de um método para outro.
Por outro lado, é bem possível exagerar e passar por tuplas, onde você só consegue adivinhar o que elas contêm. Se você usar uma tupla entre as classes, talvez seja melhor criar uma classe concreta.
Usadas com moderação, é claro, as tuplas podem levar a um código mais conciso e legível. Você pode procurar em C ++, STL e Boost exemplos de como as Tuplas são usadas em outros idiomas, mas no final, todos precisaremos experimentar para descobrir como eles se encaixam melhor no ambiente .NET.
fonte
Tuplas são um recurso inútil da estrutura no .NET 4. Acho que uma grande oportunidade foi perdida no C # 4.0. Eu adoraria ter tuplas com membros nomeados, para que você pudesse acessar os vários campos de uma tupla por nome, em vez de Valor1 , Valor2 , etc ...
Isso exigiria uma alteração de idioma (sintaxe), mas teria sido muito útil.
fonte
let success, value = MethodThatReturnsATuple()
Eu pessoalmente nunca usaria uma Tupla como um tipo de retorno, porque não há indicação do que os valores representam. As tuplas têm alguns usos valiosos porque, diferentemente dos objetos, são tipos de valor e, portanto, entendem a igualdade. Por esse motivo, utilizarei-as como chaves de dicionário se precisar de uma chave de várias partes ou como chave de uma cláusula GroupBy se desejar agrupar por várias variáveis e não desejar agrupamentos aninhados (Quem nunca deseja agrupamentos aninhados?). Para superar o problema com extrema verbosidade, você pode criá-los com um método auxiliar. Lembre-se de que você está acessando membros com frequência (por meio do Item1, Item2, etc), provavelmente deve usar uma construção diferente, como uma estrutura ou uma classe anônima.
fonte
Eu usei tuplas, tanto a
Tuple
quanto a novaValueTuple
, em vários cenários diferentes e cheguei à seguinte conclusão: não use .Toda vez, eu encontrava os seguintes problemas:
Minha opinião é que as tuplas são um detrimento, não um recurso, do C #.
Eu tenho críticas um pouco parecidas, mas muito menos duras, sobre
Func<>
eAction<>
. Isso é útil em muitas situações, especialmente as simplesAction
e asFunc<type>
variantes, mas, além disso, descobri que criar um tipo de delegado é mais elegante, legível, sustentável e oferece mais recursos, comoref
/out
parâmetros.fonte
ValueTuple
- e também não gosto delas. Primeiro, a nomeação não é forte, é mais uma sugestão, muito fácil de estragar, porque pode ser atribuída implicitamente a umTuple
ouValueTuple
com o mesmo número e tipos de itens. Segundo, a falta de herança e a necessidade de copiar e colar toda a definição de um lugar para outro ainda são desvantagens.