Como inicializar facilmente uma lista de tuplas?

286

Eu amo tuplas . Eles permitem que você agrupe rapidamente informações relevantes sem precisar escrever uma estrutura ou classe para elas. Isso é muito útil ao refatorar um código muito localizado.

Inicializar uma lista deles, no entanto, parece um pouco redundante.

var tupleList = new List<Tuple<int, string>>
{
    Tuple.Create( 1, "cow" ),
    Tuple.Create( 5, "chickens" ),
    Tuple.Create( 1, "airplane" )
};

Não existe uma maneira melhor? Eu adoraria uma solução ao longo das linhas do inicializador de dicionário .

Dictionary<int, string> students = new Dictionary<int, string>()
{
    { 111, "bleh" },
    { 112, "bloeh" },
    { 113, "blah" }
};

Não podemos usar uma sintaxe semelhante?

Steven Jeuris
fonte
2
Nesse caso, por que você não usaria um dicionário em vez de uma lista de tuplas?
Ed S.
17
@ Ed S .: A Dictionarynão permite chaves duplicadas.
Steven Jeuris
2
@ Eds .: Toda vez que não é uma tupla dupla, um item é lavável / ordenável e único.
Bom ponto, não percebeu a chave duplicada.
Ed S.

Respostas:

279

O c # 7.0 permite fazer isso:

  var tupleList = new List<(int, string)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

Se você não precisa de um List, mas apenas de um array, pode fazer:

  var tupleList = new(int, string)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

E se você não gosta de "Item1" e "Item2", pode:

  var tupleList = new List<(int Index, string Name)>
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

ou para uma matriz:

  var tupleList = new (int Index, string Name)[]
  {
      (1, "cow"),
      (5, "chickens"),
      (1, "airplane")
  };

o que permite: tupleList[0].IndexetupleList[0].Name

Estrutura 4.6.2 e abaixo

Você deve instalar a System.ValueTuplepartir do Nuget Package Manager.

Quadro 4.7 e superior

Está embutido na estrutura. Você não instalar System.ValueTuple. Na verdade, remova-o e exclua-o do diretório bin.

nota: na vida real, eu não seria capaz de escolher entre vacas, galinhas ou avião. Eu ficaria muito rasgado.

toddmo
fonte
2
Isso pode ser usado em um .net core 2.0?
Алекса Јевтић
2
@ АлексаЈевтић, System.ValueTuplesuporta o núcleo 2.0. Mas, tente primeiro sem o Nuget, pois pacotes desnecessários podem causar problemas. Então, de um jeito ou de outro, sim. Use c # v7 ou superior, se possível.
precisa saber é
1
Resposta absolutamente perfeita! Obrigado!
Vitox em 21/01
224

Sim! Isso é possível .

A sintaxe {} do inicializador de coleção funciona em qualquer tipo IEnumerable que possua um método Add com a quantidade correta de argumentos. Sem se preocupar em como isso funciona embaixo das cobertas, isso significa que você pode simplesmente estender da Lista <T> , adicionar um método Add personalizado para inicializar seu T e pronto!

public class TupleList<T1, T2> : List<Tuple<T1, T2>>
{
    public void Add( T1 item, T2 item2 )
    {
        Add( new Tuple<T1, T2>( item, item2 ) );
    }
}

Isso permite que você faça o seguinte:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};
Steven Jeuris
fonte
39
Uma desvantagem é ter que escrever a classe. Como você, eu amo tuplas, porque não preciso escrever uma classe ou estrutura.
Good11e #
2
@ BillW Não tenho certeza se este é o post exato, ... mas eu sei que Jon Skeet já escreveu sobre isso antes .
9119 Steven Jeuris
1
isso funciona groceryList[0] == groceryList[1]ou é necessário implementar uma comparação?
Byyo 27/04/16
Criei um pacote Nuget com métodos Add no ICollection para economizar aos outros o tempo necessário para manter esse código. Veja aqui o pacote: nuget.org/packages/Naos.Recipes.TupleInitializers Veja aqui o código: github.com/NaosProject/Naos.Recipes/blob/master/… Isso incluirá um arquivo cs na sua solução em ".Naos .Recipes" da pasta, para que você não tem que arrastar em torno de uma dependência assembly
SFun28
83

O C # 6 adiciona um novo recurso apenas para isso: extension Adicione métodos. Isso sempre foi possível para o VB.net, mas agora está disponível em C #.

Agora você não precisa adicionar Add()métodos diretamente às suas classes, pode implementá-los como métodos de extensão. Ao estender qualquer tipo enumerável com um Add()método, você poderá usá-lo nas expressões do inicializador de coleção. Para que você não precise mais derivar explicitamente das listas ( como mencionado em outra resposta ), basta estendê-las.

public static class TupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
            T1 item1, T2 item2)
    {
        list.Add(Tuple.Create(item1, item2));
    }

    public static void Add<T1, T2, T3>(this IList<Tuple<T1, T2, T3>> list,
            T1 item1, T2 item2, T3 item3)
    {
        list.Add(Tuple.Create(item1, item2, item3));
    }

    // and so on...
}

Isso permitirá que você faça isso em qualquer classe que implemente IList<>:

var numbers = new List<Tuple<int, string>>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
    { 4, "four" },
    { 5, "five" },
};
var points = new ObservableCollection<Tuple<double, double, double>>
{
    { 0, 0, 0 },
    { 1, 2, 3 },
    { -4, -2, 42 },
};

É claro que você não está restrito a estender coleções de tuplas, pode ser para coleções de qualquer tipo específico para o qual você deseja a sintaxe especial.

public static class BigIntegerListExtensions
{
    public static void Add(this IList<BigInteger> list,
        params byte[] value)
    {
        list.Add(new BigInteger(value));
    }

    public static void Add(this IList<BigInteger> list,
        string value)
    {
        list.Add(BigInteger.Parse(value));
    }
}

var bigNumbers = new List<BigInteger>
{
    new BigInteger(1), // constructor BigInteger(int)
    2222222222L,       // implicit operator BigInteger(long)
    3333333333UL,      // implicit operator BigInteger(ulong)
    { 4, 4, 4, 4, 4, 4, 4, 4 },               // extension Add(byte[])
    "55555555555555555555555555555555555555", // extension Add(string)
};

O C # 7 adicionará suporte para tuplas criadas no idioma, embora elas sejam de um tipo diferente (em System.ValueTuplevez disso). Portanto, seria bom adicionar sobrecargas para tuplas de valor, assim você tem a opção de usá-las também. Infelizmente, não há conversões implícitas definidas entre os dois.

public static class ValueTupleListExtensions
{
    public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
        ValueTuple<T1, T2> item) => list.Add(item.ToTuple());
}

Dessa forma, a inicialização da lista ficará ainda melhor.

var points = new List<Tuple<int, int, int>>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};

Mas, em vez de passar por todo esse problema, pode ser melhor mudar para o uso ValueTupleexclusivo.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};
Jeff Mercado
fonte
Finalmente, dei uma olhada decente nisso enquanto atualizava minha biblioteca para C # 6.0. Embora pareça bom à primeira vista, prefiro minha solução postada anteriormente sobre isso porque acho TupleList<int, string>.que é mais legível do que List<Tuple<int, string>>.
Steven Jeuris 28/10
1
Isso é algo que um alias de tipo pode corrigir. Concedido o apelido em si não pode ser genérico, o que pode ser preferível. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado
Criei um pacote Nuget com métodos Add no ICollection para economizar aos outros o tempo necessário para manter esse código. Veja aqui o pacote: nuget.org/packages/Naos.Recipes.TupleInitializers Veja aqui o código: github.com/NaosProject/Naos.Recipes/blob/master/… Isso incluirá um arquivo cs na sua solução em ".Naos .Recipes ", para que você não precise arrastar uma dependência de assembly.
SFun28
42

Você pode fazer isso chamando o construtor toda vez que é um pouco melhor

var tupleList = new List<Tuple<int, string>>
{
    new Tuple<int, string>(1, "cow" ),
    new Tuple<int, string>( 5, "chickens" ),
    new Tuple<int, string>( 1, "airplane" )
};
Kevin Holditch
fonte
6
Eu não poderia obter o código original para o trabalho, para que eu tenha alterado-lo para o que eu acho que deveria ser ... o que pode inverter a sua opinião sobre se a sintaxe "é um pouco melhor" depois de tudo :)
onedaywhen
5
pelo menos uso Tuple.Createem vez e você pode inferir os argumentos de tipo
Dave Cousineau
28

Pergunta antiga, mas é o que normalmente faço para tornar as coisas um pouco mais legíveis:

Func<int, string, Tuple<int, string>> tc = Tuple.Create;

var tupleList = new List<Tuple<int, string>>
{
    tc( 1, "cow" ),
    tc( 5, "chickens" ),
    tc( 1, "airplane" )
};
Asad Saeeduddin
fonte
Também funciona em versões antigas do .NET!
Ed Bayiates
1

Super Duper Old Eu sei, mas acrescentaria meu artigo sobre o Linq e lambdas de continuação em métodos com o C # 7. Tento usar tuplas nomeadas como substitutas de DTOs e projeções anônimas quando reutilizadas em uma classe. Sim, para zombar e testar, você ainda precisa de aulas, mas fazer coisas em linha e distribuir uma aula é bom ter essa nova opção IMHO. Você pode instancia-los de

  1. Instanciação direta
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Fora de uma coleção existente, e agora você pode retornar tuplas bem digitadas, como as projeções anônimas costumavam ser feitas.
public class Hold
{
    public int Id { get; set; }
    public string Name { get; set; }
}

//In some method or main console app:
var holds = new List<Hold> { new Hold { Id = 1, Name = "Me" }, new Hold { Id = 2, Name = "You" } };
var anonymousProjections = holds.Select(x => new { SomeNewId = x.Id, SomeNewName = x.Name });
var namedTuples = holds.Select(x => (TupleId: x.Id, TupleName: x.Name));
  1. Reutilize as tuplas posteriormente com métodos de agrupamento ou use um método para construí-las embutidas em outra lógica:
//Assuming holder class above making 'holds' object
public (int Id, string Name) ReturnNamedTuple(int id, string name) => (id, name);
public static List<(int Id, string Name)> ReturnNamedTuplesFromHolder(List<Hold> holds) => holds.Select(x => (x.Id, x.Name)).ToList();
public static void DoSomethingWithNamedTuplesInput(List<(int id, string name)> inputs) => inputs.ForEach(x => Console.WriteLine($"Doing work with {x.id} for {x.name}"));

var namedTuples2 = holds.Select(x => ReturnNamedTuple(x.Id, x.Name));
var namedTuples3 = ReturnNamedTuplesFromHolder(holds);
DoSomethingWithNamedTuplesInput(namedTuples.ToList());
djangojazz
fonte
Você me faz sentir velha. :) Talvez esteja faltando alguma coisa aqui, mas a inicialização de 'hold' não é concisa. Esse era o ponto exato da pergunta.
Steven Jeuris
@ Steven Jeuris Não, você estava certo, eu copiei e colei o código errado para o ponto 1. Preciso ir um pouco mais devagar antes de clicar em 'enviar'.
precisa
0

Por que gosta de tuplas? É como tipos anônimos: sem nomes. Não consigo entender a estrutura dos dados.

Eu gosto de aulas clássicas

class FoodItem
{
     public int Position { get; set; }
     public string Name { get; set; }
}

List<FoodItem> list = new List<FoodItem>
{
     new FoodItem { Position = 1, Name = "apple" },
     new FoodItem { Position = 2, Name = "kiwi" }
};
Alexey Obukhov
fonte
3
Como afirmei: "Eles permitem que você agrupe rapidamente informações relevantes sem precisar escrever uma estrutura ou classe para elas". Caso essa classe seja usada apenas dentro de um único método como uma estrutura de dados auxiliar, eu prefiro usar Tuplas, para não preencher um espaço para nome com uma classe desnecessária.
9186 Steven Jeuris
1
Você está pensando na classe Tuple mais antiga (não em struct). C # 7 adicionou tuplas baseada struct (apoiado pelo novo tipo ValueTuple) que podem ter nomes, para que possa compreender a estrutura de dados
Steve Niles
0

Uma técnica que eu acho que é um pouco mais fácil e que não foi mencionada antes aqui:

var asdf = new [] { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
}.ToList();

Eu acho que é um pouco mais limpo do que:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};
Alex Dresko
fonte
É mais fácil provar se você prefere mencionar tipos explicitamente ou não, da mesma forma que usar varou não. Pessoalmente, prefiro digitar explicitamente (quando não é duplicado, por exemplo, no construtor).
Steven Jeuris
-4
    var colors = new[]
    {
        new { value = Color.White, name = "White" },
        new { value = Color.Silver, name = "Silver" },
        new { value = Color.Gray, name = "Gray" },
        new { value = Color.Black, name = "Black" },
        new { value = Color.Red, name = "Red" },
        new { value = Color.Maroon, name = "Maroon" },
        new { value = Color.Yellow, name = "Yellow" },
        new { value = Color.Olive, name = "Olive" },
        new { value = Color.Lime, name = "Lime" },
        new { value = Color.Green, name = "Green" },
        new { value = Color.Aqua, name = "Aqua" },
        new { value = Color.Teal, name = "Teal" },
        new { value = Color.Blue, name = "Blue" },
        new { value = Color.Navy, name = "Navy" },
        new { value = Color.Pink, name = "Pink" },
        new { value = Color.Fuchsia, name = "Fuchsia" },
        new { value = Color.Purple, name = "Purple" }
    };
    foreach (var color in colors)
    {
        stackLayout.Children.Add(
            new Label
            {
                Text = color.name,
                TextColor = color.value,
            });
        FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
    }

this is a Tuple<Color, string>
Egi Messito
fonte
Como você obtém a sintaxe do valor / nome com uma tupla?
Andreas Reiff
Compiler diz que tipo anônimo não são implicitamente conversível para Tuple
cthepenier