Come inizializzare facilmente un elenco di Tuple?


287

Adoro le tuple . Ti consentono di raggruppare rapidamente le informazioni pertinenti senza dover scrivere una struttura o una classe per esse. Ciò è molto utile durante il refactoring di codice molto localizzato.

Inizializzare un elenco di questi sembra tuttavia un po 'ridondante.

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

Non c'è un modo migliore? Mi piacerebbe una soluzione come l' inizializzatore del dizionario .

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

Non possiamo usare una sintassi simile?


2
In questo caso, perché non dovresti usare un dizionario invece di un elenco di Tuple?
Ed S.

17
@Ed S .: A Dictionarynon consente chiavi duplicate.
Steven Jeuris,

2
@EdS .: Ogni volta non è una doppia tupla in cui un oggetto è hash / ordinabile e unico.

Buon punto, non ho notato la chiave duplicata.
Ed S.

Risposte:


282

c # 7.0 ti permette di fare questo:

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

Se non ti serve un List, ma solo un array, puoi fare:

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

E se non ti piacciono "Item1" e "Item2", puoi fare:

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

o per un array:

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

che ti permette di fare: tupleList[0].IndexetupleList[0].Name

Quadro 4.6.2 e precedenti

È necessario installare System.ValueTupleda Nuget Package Manager.

Framework 4.7 e versioni successive

È integrato nel framework. Evitare Non installare System.ValueTuple. In effetti, rimuoverlo ed eliminarlo dalla directory bin.

nota: nella vita reale, non sarei in grado di scegliere tra mucca, galline o aereo. Sarei davvero lacerato.


2
Può essere usato su un core .net 2.0?
Алекса Јевтић,

2
@ АлексаЈевтић, System.ValueTuplesupporta core 2.0. Ma provalo prima senza il Nuget, poiché i pacchetti non necessari possono causare problemi. Quindi, in un modo o nell'altro, sì. Utilizzare c # v7 o superiore, se possibile.
toddmo

1
Risposta assolutamente perfetta! Grazie!
Vitox,

224

Sì! Questo è possibile .

La sintassi {} dell'inizializzatore di raccolte funziona su qualsiasi tipo IEnumerable che ha un metodo Add con la quantità corretta di argomenti. Senza preoccuparti di come funziona sotto le copertine, ciò significa che puoi semplicemente estendere dall'elenco <T> , aggiungere un metodo Add personalizzato per inizializzare la T e il gioco è fatto!

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

Questo ti permette di fare quanto segue:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};

39
Un aspetto negativo è dover scrivere la classe. Come te, amo le tuple perché non devo scrivere un corso o una struttura.
addio

2
@BillW Non troppo sicuro che questo sia il post esatto, ... ma so che Jon Skeet ne ha già scritto .
Steven Jeuris,

1
funziona groceryList[0] == groceryList[1]o deve essere implementato un confronto?
Byyo,

Ho creato un pacchetto Nuget con metodi Add su ICollection per risparmiare agli altri il tempo di dover mantenere questo codice. Vedi qui per il pacchetto: nuget.org/packages/Naos.Recipes.TupleInitializers Vedi qui per il codice: github.com/NaosProject/Naos.Recipes/blob/master/… Questo includerà un file cs nella tua soluzione sotto un ".Naos .Recipes ", quindi non è necessario trascinare una dipendenza assembly
SFun28

83

C # 6 aggiunge una nuova funzionalità proprio per questo: estensione Aggiungi metodi. Questo è sempre stato possibile per VB.net ma ora è disponibile in C #.

Ora non devi aggiungere Add()metodi direttamente alle tue classi, puoi implementarli come metodi di estensione. Quando estendi qualsiasi tipo enumerabile con un Add()metodo, sarai in grado di usarlo nelle espressioni di inizializzazione della raccolta. Quindi non devi più derivare esplicitamente dagli elenchi ( come menzionato in un'altra risposta ), puoi semplicemente estenderlo.

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...
}

Ciò ti consentirà di farlo su qualsiasi classe che implementa 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 },
};

Ovviamente non sei limitato all'estensione di raccolte di tuple, può essere per raccolte di qualsiasi tipo specifico per cui desideri la sintassi speciale.

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)
};

C # 7 aggiungerà il supporto per le tuple integrate nella lingua, anche se saranno di tipo diverso ( System.ValueTupleinvece). Quindi sarebbe bene aggiungere sovraccarichi per le tuple di valore in modo da avere anche la possibilità di usarli. Sfortunatamente, non ci sono conversioni implicite definite tra i due.

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

In questo modo l'inizializzazione dell'elenco sarà ancora più piacevole.

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

Ma invece di affrontare tutti questi problemi, potrebbe essere meglio passare all'utilizzo ValueTupleesclusivo.

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

Ho finalmente dato un'occhiata decente a questo mentre aggiornavo la mia libreria a C # 6.0. Anche se sembra buono a colpo d'occhio, preferisco la mia soluzione precedentemente pubblicata rispetto a questa perché trovo TupleList<int, string>.che sia più leggibile di List<Tuple<int, string>>.
Steven Jeuris,

1
È qualcosa che un alias di tipo può risolvere. Concesso lo stesso alias non può essere generico, il che può essere preferibile. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado,

Ho creato un pacchetto Nuget con metodi Add su ICollection per risparmiare agli altri il tempo di dover mantenere questo codice. Vedi qui per il pacchetto: nuget.org/packages/Naos.Recipes.TupleInitializers Vedi qui per il codice: github.com/NaosProject/Naos.Recipes/blob/master/… Questo includerà un file cs nella tua soluzione sotto un ".Naos .Recipes ", quindi non è necessario trascinare una dipendenza assembly
SFun28

42

Puoi farlo chiamando il costruttore ogni volta che è leggermente migliore

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" )
};

6
Non sono riuscito a far funzionare il codice originale, quindi l'ho modificato in quello che penso dovrebbe essere ... il che potrebbe invertire la tua opinione sul fatto che la sintassi "sia leggermente migliore" dopo tutto :)
onedaywhen

5
almeno usa Tuple.Createinvece e puoi dedurre gli argomenti di tipo
Dave Cousineau,

28

Vecchia domanda, ma questo è ciò che faccio in genere per rendere le cose un po 'più leggibili:

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" )
};

Funziona anche su vecchie versioni di .NET!
Ed Bayiates,

1

Super Duper Vecchio Lo so, ma aggiungerei il mio pezzo sull'uso di Linq e la continuazione lambdas sui metodi con l'uso di C # 7. Cerco di usare tuple nominate come sostituti di DTO e proiezioni anonime quando riutilizzate in una classe. Sì, per deridere e testare hai ancora bisogno di lezioni ma fare cose in linea e passare in classe è bello avere questa nuova opzione IMHO. Puoi istanziarli da

  1. Istantanea diretta
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Fuori da una raccolta esistente, e ora puoi restituire tuple ben scritte simili a come si facevano in precedenza le proiezioni anonime.
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. Riutilizza le tuple in seguito con metodi di raggruppamento o usa un metodo per costruirle in linea in un'altra logica:
//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());

Mi fai sentire vecchio. :) Potrebbe mancare qualcosa qui, ma l'inizializzazione di "hold" non è affatto concisa. Questo era il punto esatto della domanda.
Steven Jeuris,

@Steven Jeuris No, avevi ragione, ho copiato e incollato il bit sbagliato di codice per il punto 1. Devo andare un po 'più lentamente prima di premere' submit '.
Djangojazz,

0

Perché ti piacciono le tuple? È come tipi anonimi: nessun nome. Non riesco a capire la struttura dei dati.

Mi piacciono le lezioni classiche

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" }
};

3
Come ho affermato: "Ti consentono di raggruppare rapidamente le informazioni pertinenti senza dover scrivere una struttura o una classe per esse". Nel caso in cui questa classe venga utilizzata esclusivamente all'interno di un singolo metodo come struttura di dati di supporto, preferisco usare Tuple, quindi non popolare uno spazio dei nomi con una classe non necessaria.
Steven Jeuris,

1
Stai pensando alla classe Tuple più vecchia (non strutt). C # 7 ha aggiunto tuple basate sulla struttura (supportate dal nuovo tipo ValueTuple) che possono avere nomi, in modo da poter comprendere la struttura dei dati
Steve Niles,

0

Una tecnica che penso sia un po 'più semplice e che non è stata menzionata prima qui:

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

Penso che sia un po 'più pulito di:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};

Ciò dipende più dal fatto che tu preferisca menzionare esplicitamente i tipi o meno, nella stessa maniera in cui li usi varo meno. Personalmente preferisco la digitazione esplicita (quando non è duplicata, ad esempio nel costruttore).
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>

Come si ottiene la sintassi del valore / nome con una Tupla?
Andreas Reiff,

Il compilatore dice che i tipi anonimi non sono implicitamente convertibili in Tupla
cthepenier,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.