Exemplo prático onde Tuple pode ser usado em .Net 4.0?

97

Eu vi a tupla introduzida no .Net 4, mas não consigo imaginar onde ela pode ser usada. Sempre podemos fazer uma classe Custom ou Struct.

Amitabh
fonte
13
Talvez este seja um bom lugar para observar que este tópico é muito antigo. Veja o que aconteceu no C # 7 !
maf-soft

Respostas:

83

Esse é o ponto - é mais conveniente não criar uma classe ou estrutura personalizada o tempo todo. É uma melhoria como Actionou Func... você mesmo pode fazer esses tipos, mas é conveniente que eles existam na estrutura.

tanascius
fonte
5
Provavelmente vale a pena ressaltar do MSDN : "Qualquer membro estático público deste tipo é seguro para thread. Não é garantido que nenhum membro de instância seja seguro para thread. "
Matt Borja
Bem, talvez os designers da linguagem devessem ter facilitado a criação de tipos personalizados na hora, então? Então, poderíamos manter a mesma sintaxe em vez de introduzir outra?
Thomas Eyde,
@ThomasEyde que é exatamente o que eles têm feito nos últimos 15 anos. É assim que os membros corporais de expressão foram adicionados e agora valorizam as tuplas. Aulas de estilo registro faltou pouco para a parte de trás corte em agosto (9 meses antes desta comentários) para esta versão do C # 7, e são, provavelmente, saindo em C # 8. Observe também que o valor tuplas oferecer valor igualdade onde as aulas planície de idade não . Introduzir tudo isso em 2002 exigiria presciência
Panagiotis Kanavos
o que você quer dizer com convenient not to make a custom class. Você quer dizer criar uma instância de classe personalizada usando =new..()?
Alex
75

Com tuplas, você pode facilmente implementar um dicionário bidimensional (ou n-dimensional para esse assunto). Por exemplo, você pode usar esse dicionário para implementar um mapeamento de câmbio:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);

decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
MarioVW
fonte
Prefiro usar um Enum para as abreviações dos países. Isso é possível?
Zack
1
Claro, ele deve funcionar sem problemas, pois enums são tipos de valor.
MarioVW de
@MarioVW Também pode ser realizado usando array multidimensional. Como a tupla faz alguma diferença?
Alex
26

Há um excelente artigo na revista MSDN que fala sobre as dores de barriga e as considerações de design que envolveram a adição de Tuple ao BCL. A escolha entre um tipo de valor e um tipo de referência é particularmente interessante.

Como o artigo deixa claro, a força motriz por trás do Tuple eram tantos grupos dentro da Microsoft tendo um uso para ele, a equipe F # na frente. Embora não mencionada, acho que a nova palavra-chave "dynamic" em C # (e VB.NET) também teve algo a ver com isso, tuplas são muito comuns em linguagens dinâmicas.

Caso contrário, não é particularmente superior a criar seu próprio poco, pelo menos você pode dar aos membros um nome melhor.


ATUALIZAÇÃO: devido a uma grande revisão no C # versão 7, agora recebendo muito mais amor pela sintaxe. Anúncio preliminar nesta postagem do blog .

Hans Passant
fonte
23

Aqui está um pequeno exemplo - digamos que você tenha um método que precisa pesquisar o identificador de um usuário e o endereço de e-mail, dado um ID de usuário. Você sempre pode fazer uma classe customizada que contenha esses dados, ou usar um parâmetro ref / out para esses dados, ou você pode apenas retornar uma Tupla e ter uma assinatura de método agradável sem ter que criar um novo POCO.

public static void Main(string[] args)
{
    int userId = 0;
    Tuple<string, string> userData = GetUserData(userId);
}

public static Tuple<string, string> GetUserData(int userId)
{
    return new Tuple<string, string>("Hello", "World");
}
Tejs
fonte
10
Este é um bom exemplo, porém, não justifica o uso de Tupla.
Amitabh
7
Uma tupla é um ajuste decente aqui, pois você está retornando valores distintos; mas uma tupla brilha mais quando você retorna vários valores de tipos diferentes .
Mark Rushakoff
21
Outro bom exemplo seria int.ExperimenteParse, já que você poderia eliminar o parâmetro de saída e, em vez disso, usar uma Tupla. Portanto, você poderia ter Tuple<bool, T> TryParse<T>(string input)e, em vez de usar um parâmetro de saída, obter os dois valores de volta em uma tupla.
Tejs
3
na verdade, é exatamente isso que acontece quando você chama qualquer método TryParse do F #.
Joel Mueller
23

Usei uma tupla para resolver o Problema 11 do Projeto Euler :

class Grid
{
    public static int[,] Cells = { { 08, 02, 22, // whole grid omitted

    public static IEnumerable<Tuple<int, int, int, int>> ToList()
    {
        // code converts grid to enumeration every possible set of 4 per rules
        // code omitted
    }
}

Agora posso resolver todo o problema com:

class Program
{
    static void Main(string[] args)
    {
        int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
        Console.WriteLine("Maximum product is {0}", product);
    }
}

Eu poderia ter usado um tipo personalizado para isso, mas seria exatamente como Tupla .

Craig Stuntz
fonte
16

A sintaxe de tupla do C # é ridiculamente volumosa, então tuplas são difíceis de declarar. E não tem correspondência de padrões, então eles também são difíceis de usar.

Mas, ocasionalmente, você deseja apenas um agrupamento ad-hoc de objetos sem criar uma classe para ele. Por exemplo, digamos que eu quisesse agregar uma lista, mas quisesse dois valores em vez de um:

// sum and sum of squares at the same time
var x =
    Enumerable.Range(1, 100)
    .Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));

Em vez de combinar uma coleção de valores em um único resultado, vamos expandir um único resultado em uma coleção de valores. A maneira mais fácil de escrever esta função é:

static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
    Tuple<T, State> res;
    while ((res = f(seed)) != null)
    {
        yield return res.Item1;
        seed = res.Item2;
    }
}

fconverte algum estado em uma tupla. Retornamos o primeiro valor da tupla e definimos nosso novo estado para o segundo valor. Isso nos permite reter o estado durante a computação.

Você o usa como tal:

// return 0, 2, 3, 6, 8
var evens =
    Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
    .ToList();

// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
    Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
    .Take(10).ToList();

evensé bastante simples, mas fibsé um pouco mais inteligente. Sua stateé realmente um tuplo que detém FIB (n-2) e FIB (n-1), respectivamente.

Julieta
fonte
4
+1 Tupla.Criar é uma abreviação útil paranew Tuple<Guid,string,...>
AaronLS
@Juliet Qual é o uso da palavra-chave State
irfandar
7

Eu não gosto do abuso deles, já que eles produzem código que não se explica, mas eles são incríveis para implementar chaves compostas em tempo real, uma vez que implementam IStructuralEquatable e IStructuralComparable (para usar tanto para pesquisa quanto para ordenação finalidades).

E eles combinam todos os hashcodes de seus itens, internamente; por exemplo, aqui está o GetHashCode de Tuple (retirado do ILSpy):

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
    }
Notoriousxl
fonte
7

Tuplas são ótimas para fazer várias operações assíncronas de E / S de uma vez e retornar todos os valores juntos. Aqui estão os exemplos de como fazer isso com e sem tupla. As tuplas podem realmente tornar seu código mais claro!

Sem (aninhamento desagradável!):

Task.Factory.StartNew(() => data.RetrieveServerNames())
    .ContinueWith(antecedent1 =>
        {
            if (!antecedent1.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
                Task.Factory.StartNew(() => data.RetrieveLogNames())
                    .ContinueWith(antecedent2 =>
                        {
                            if (antecedent2.IsFaulted)
                            {
                                LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
                                Task.Factory.StartNew(() => data.RetrieveEntryTypes())
                                    .ContinueWith(antecedent3 =>
                                        {
                                            if (!antecedent3.IsFaulted)
                                            {
                                                EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
                                            }
                                        });
                            }
                        });
            }
        });

Com tupla

Task.Factory.StartNew(() =>
    {
        List<string> serverNames = data.RetrieveServerNames();
        List<string> logNames = data.RetrieveLogNames();
        List<string> entryTypes = data.RetrieveEntryTypes();
        return Tuple.Create(serverNames, logNames, entryTypes);
    }).ContinueWith(antecedent =>
        {
            if (!antecedent.IsFaulted)
            {
                ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
                LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
                EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
            }
        });

Se você estava usando uma função anônima com um tipo implícito de qualquer maneira , você não está tornando o código menos claro usando a Tupla. Sintonizando novamente uma tupla de um método? Use com moderação quando a clareza do código for fundamental, na minha humilde opinião. Eu sei que a programação funcional em C # é difícil de resistir, mas temos que considerar todos os antigos programadores C # desajeitados "orientados a objetos".

AndyClaw
fonte
5

Tuplas são muito usadas em linguagens funcionais que podem fazer mais coisas com elas, agora F # é uma linguagem .net 'oficial', você pode querer interoperar com ela a partir do C # e passá-las entre códigos escritos em duas linguagens.

Mant101
fonte
Tuplas também são construídas em tipos para algumas linguagens de script populares, como Python e Ruby (que também têm implementações .Net para interoperabilidade ... IronPython / Ruby).
MutantNinjaCodeMonkey
5

Eu tendo a evitar Tuplena maioria dos cenários, pois prejudica a legibilidade. Contudo,Tuple é útil quando você precisa agrupar dados não relacionados.

Por exemplo, suponha que você tenha uma lista de carros e as cidades em que foram comprados:

Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle

Você deseja agregar as contagens de cada carro por cidade:

Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]

Para fazer isso, você cria um Dictionary. Você tem poucas opções:

  1. Crie um Dictionary<string, Dictionary<string, int>>.
  2. Crie um Dictionary<CarAndCity, int>.
  3. Crie um Dictionary<Tuple<string, string>, int>.

A legibilidade é perdida com a primeira opção. Isso exigirá que você escreva muito mais código.

A segunda opção funciona e é sucinta, mas carro e cidade não estão realmente relacionados e provavelmente não pertencem a uma mesma classe.

A terceira opção é sucinta e limpa. É um bom uso de Tuple.

John Kurlak
fonte
4

Alguns exemplos em cima da minha cabeça:

  • Uma localização X e Y (e Z, se quiser)
  • a largura e altura
  • Qualquer coisa medida ao longo do tempo

Por exemplo, você não gostaria de incluir System.Drawing em um aplicativo da web apenas para usar Point / PointF e Size / SizeF.

James Westgate
fonte
2
Tupleé tão útil em situações críticas quanto Pointou SomeVectorpode ser útil ao fazer operações gráficas. Ele destaca dois valores que estão muito ligados um ao outro. Costumo ver propriedades chamadas StartTime e EndTime ao ler o código, mas ainda melhor do que uma data e hora e uma duração, uma Tupla força você a considerar ambos os valores sempre que estiver operando nesta área de sua lógica de negócios. Ou retornar "ei, os dados foram alterados (bool), aqui estão os dados (outro tipo)" em processamento pesado em vez de passar por ligações intensivas.
Léon Pelletier de
3

Você deve ter muito cuidado com o uso Tuplee provavelmente pensar duas vezes antes de fazer isso. Com minha experiência anterior, descobri que o uso Tupletorna o código muito difícil de ler e dar suporte no futuro. Um tempo atrás, tive que consertar alguns códigos em que tuplas eram usadas em quase todos os lugares. Em vez de pensar em modelos de objetos adequados, eles apenas usaram tuplas. Aquilo foi um pesadelo ... às vezes eu queria matar o cara que escreveu o código ...

Não quero dizer que você não deve usar Tuplee é mau ou algo assim e estou cem por cento certo de que existem algumas tarefas em que o Tupleé o melhor candidato a ser usado, mas provavelmente você deve pensar novamente, você REALMENTE precisa dele ?

Senhor abóbora
fonte
1

O melhor uso para tuplas que encontrei é quando precisar retornar mais de 1 tipo de objeto de um método, você sabe quais tipos de objeto e número eles serão, e não é uma lista longa.

Outras alternativas simples seriam usar um parâmetro 'out'

private string MyMethod(out object)

ou fazendo um dicionário

Dictionary<objectType1, objectType2>

O uso de uma tupla, entretanto, evita a criação do objeto 'de saída' ou essencialmente a necessidade de pesquisar a entrada no dicionário;

sidjames
fonte
1

Acabei de encontrar a solução de um dos meus problemas na Tupla. É como declarar uma classe no escopo de um método, mas com declaração lenta dos nomes de seus campos. Você opera com coleções de tuplas, suas instâncias únicas e, em seguida, cria uma coleção de tipo anônimo com os nomes de campo necessários, com base em sua tupla. Isso evita que você crie uma nova classe para essa finalidade.

A tarefa é escrever uma resposta JSON do LINQ sem nenhuma classe adicional:

 //I select some roles from my ORM my with subrequest and save results to Tuple list
 var rolesWithUsers = (from role in roles
                       select new Tuple<string, int, int>(
                         role.RoleName, 
                         role.RoleId, 
                         usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
                      ));

 //Then I add some new element required element to this collection
 var tempResult = rolesWithUsers.ToList();
 tempResult.Add(new Tuple<string, int, int>(
                        "Empty", 
                         -1,
                         emptyRoleUsers.Count()
                      ));

 //And create a new anonimous class collection, based on my Tuple list
 tempResult.Select(item => new
            {
                GroupName = item.Item1,
                GroupId = item.Item2,
                Count = item.Item3
            });


 //And return it in JSON
 return new JavaScriptSerializer().Serialize(rolesWithUsers);

É claro que poderíamos fazer isso declarando uma nova classe para meus grupos, mas a ideia de criar tais coleções anônimas sem declarar novas classes.

Alex
fonte
1

Bem, no meu caso, eu tive que usar uma Tupla quando descobri que não podemos usar o parâmetro out em um método assíncrono. Leia sobre isso aqui . Eu também precisava de um tipo de retorno diferente. Portanto, usei uma tupla como meu tipo de retorno e marquei o método como assíncrono.

Amostra de código abaixo.

...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...

private async Tuple<string,int> GetUserDetails(int userId)
{
    return new Tuple<string,int>("Amogh",105);
    // Note that I can also use the existing helper method (Tuple.Create).
}

Leia mais sobre Tupla aqui . Espero que isto ajude.

Amogh Natu
fonte
Presumo que você não quisesse retornar um tipo anônimo ou Array (ou conteúdo dinâmico)
ozzy432836
0

Alterar formas de objetos quando você precisa enviá-los através do fio ou passar para uma camada diferente de aplicativo e vários objetos são mesclados em um:

Exemplo:

var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();

ExtensionMethod:

public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
    {
        var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
        var customerDetails = new CustomerDetails
        {
            FirstName = customerAndAddress.Item1.Name,
            LastName = customerAndAddress.Item1.Surname,
            Title = customerAndAddress.Item1.Title,
            Dob = customerAndAddress.Item1.Dob,
            EmailAddress = customerAndAddress.Item1.Email,
            Gender = customerAndAddress.Item1.Gender,
            PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
        };

        if (mainAddress != null)
        {
            customerDetails.AddressLine1 =
                !string.IsNullOrWhiteSpace(mainAddress.HouseName)
                    ? mainAddress.HouseName
                    : mainAddress.HouseNumber;
            customerDetails.AddressLine2 =
                !string.IsNullOrWhiteSpace(mainAddress.Street)
                    ? mainAddress.Street
                    : null;
            customerDetails.AddressLine3 =
                !string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
            customerDetails.AddressLine4 =
                !string.IsNullOrWhiteSpace(mainAddress.County)
                    ? mainAddress.County
                    : null;
            customerDetails.PostCode = mainAddress.PostCode;
        }
...
        return customerDetails;
    }
Matas Vaitkevicius
fonte
0

Um parâmetro out é ótimo quando há apenas alguns valores que precisam ser retornados, mas quando você começa a encontrar 4, 5, 6 ou mais valores que precisam ser retornados, pode ficar complicado. Outra opção para retornar vários valores é criar e retornar uma classe / estrutura definida pelo usuário ou usar uma Tupla para empacotar todos os valores que precisam ser retornados por um método.

A primeira opção, usando uma classe / estrutura para retornar os valores, é direta. Basta criar o tipo (neste exemplo, é uma estrutura) assim:

public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}

A segunda opção, usando uma Tupla, é uma solução ainda mais elegante do que usar um objeto definido pelo usuário. Uma tupla pode ser criada para conter qualquer número de valores de vários tipos. Além disso, os dados que você armazena na Tupla são imutáveis; depois de adicionar os dados à tupla por meio do construtor ou do método estático Create, esses dados não podem ser alterados. As tuplas podem aceitar até e incluindo oito valores separados. Se precisar retornar mais de oito valores, você precisará usar a classe especial de Tupla: Classe de Tupla Ao criar uma Tupla com mais de oito valores, você não pode usar o método estático Create - em vez disso, você deve usar o construtor da classe. É assim que você criaria uma tupla de 10 valores inteiros:

var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));

Obviamente, você pode continuar adicionando mais tuplas ao final de cada tupla incorporada, criando qualquer tupla de tamanho de que você precisa.

Yashwanth Chowdary Kata
fonte
0

Apenas para prototipagem - Tuplas não fazem sentido. É conveniente usá-los, mas é apenas um atalho! Para protótipos - tudo bem. Apenas certifique-se de excluir este código mais tarde.

É fácil de escrever, difícil de ler. Não tem vantagens visíveis sobre classes, classes internas, classes anônimas etc.

bunny1985
fonte
0

Bem, eu tentei 3 maneiras de resolver o mesmo problema em C # 7 e encontrei um caso de uso para tuplas.

Trabalhar com dados dinâmicos em projetos da web às vezes pode ser uma dor ao mapear etc.

Gosto da maneira como a Tupla é mapeada automaticamente para item1, item2, itemN, o que parece mais robusto para mim do que usar índices de matriz, onde você pode ser pego em um item fora do índice, ou usar o tipo anônimo, onde você pode digitar incorretamente um nome de propriedade.

Parece que um DTO foi criado gratuitamente apenas usando um Tuple e posso acessar todas as propriedades usando o itemN, que parece mais uma digitação estática, sem ter que criar um DTO separado para esse propósito.

using System;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            var tuple = GetTuple();
            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
            Console.WriteLine(tuple.Item3);
            Console.WriteLine(tuple);

            Console.WriteLine("---");

            var dyn = GetDynamic();
            Console.WriteLine(dyn.First);
            Console.WriteLine(dyn.Last);
            Console.WriteLine(dyn.Age);
            Console.WriteLine(dyn);

            Console.WriteLine("---");

            var arr = GetArray();
            Console.WriteLine(arr[0]);
            Console.WriteLine(arr[1]);
            Console.WriteLine(arr[2]);
            Console.WriteLine(arr);

            Console.Read();

            (string, string, int) GetTuple()
            {
                return ("John", "Connor", 1);
            }

            dynamic GetDynamic()
            {
                return new { First = "John", Last = "Connor", Age = 1 };
            }

            dynamic[] GetArray()
            {
                return new dynamic[] { "John", "Connor", 1 };
            }
        }
    }
}
ozzy432836
fonte