Jak łatwo zainicjować listę krotek?

287

Uwielbiam krotki . Pozwalają szybko grupować istotne informacje bez konieczności pisania dla nich struktury lub klasy. Jest to bardzo przydatne podczas refaktoryzacji bardzo zlokalizowanego kodu.

Inicjowanie ich listy wydaje się jednak nieco zbędne.

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

Czy nie ma lepszego sposobu? Chciałbym znaleźć rozwiązanie zgodne z inicjatorem Dictionary .

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

Czy nie możemy użyć podobnej składni?

Steven Jeuris
źródło
2
W takim przypadku dlaczego nie użyłbyś słownika zamiast listy krotek?
Ed S.
17
@Ed S .: A Dictionarynie zezwala na duplikowanie kluczy.
Steven Jeuris,
2
@EdS .: Za każdym razem nie jest to podwójna krotka, w której jeden element jest haszowalny / można go zamówić i jest unikalny.
Dobra uwaga, nie zauważyłem duplikatu klucza.
Ed S.

Odpowiedzi:

280

c # 7.0 pozwala to zrobić:

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

Jeśli nie potrzebujesz Listtylko tablicy, możesz:

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

A jeśli nie lubisz „Item1” i „Item2”, możesz:

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

lub dla tablicy:

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

co pozwala ci zrobić: tupleList[0].IndexitupleList[0].Name

Framework 4.6.2 i poniżej

Musisz zainstalować System.ValueTuplez Menedżera pakietów Nuget.

Ramy 4.7 i nowsze

Jest wbudowany w ramy. Czy nie instalować System.ValueTuple. W rzeczywistości usuń go i usuń z katalogu bin.

Uwaga: W prawdziwym życiu nie byłbym w stanie wybierać między krową, kurczakiem lub samolotem. Byłbym naprawdę rozdarty.

toddmo
źródło
2
Czy można tego użyć w .net core 2.0?
Алекса Јевтић
2
@ АлексаЈевтић, System.ValueTupleobsługuje rdzeń 2.0. Ale spróbuj najpierw bez Nugeta, ponieważ niepotrzebne pakiety mogą powodować problemy. Tak czy inaczej, tak. Użyj c # v7 lub nowszej, jeśli to możliwe.
toddmo
1
Absolutnie idealna odpowiedź! Dzięki!
Vitox
224

Tak! To jest możliwe .

{} Składni kolekcji initializer działa na każdej IEnumerable typ który ma Add metody z odpowiednią ilością argumentów. Bez zastanawiania się, jak to działa pod przykryciem, oznacza to, że możesz po prostu rozszerzyć z listy <T> , dodać niestandardową metodę Add, aby zainicjować swój T , i gotowe!

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

Umożliwia to wykonanie następujących czynności:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};
Steven Jeuris
źródło
39
Minusem jest konieczność napisania zajęć. Podobnie jak ty, uwielbiam krotki, ponieważ nie muszę pisać klasy ani struktury.
goodeye,
2
@BillW Nie jestem pewien, czy to jest dokładnie ten post, ale wiem, że Jon Skeet napisał o tym wcześniej .
Steven Jeuris,
1
czy to działa groceryList[0] == groceryList[1]lub czy należy wprowadzić porównanie?
Byyo
Stworzyłem pakiet Nuget z metodami Add w ICollection, aby zaoszczędzić innym czasu na utrzymywanie tego kodu. Zobacz tutaj dla pakietu: nuget.org/packages/Naos.Recipes.TupleInitializers Zobacz tutaj kod: github.com/NaosProject/Naos.Recipes/blob/master/… To spowoduje dołączenie pliku cs do twojego rozwiązania pod „.Naos Folder .Recipes ”, więc nie musisz przeciągać zależności od złożenia
SFun28
83

C # 6 dodaje nową funkcję tylko do tego: rozszerzenie Dodaj metody. Zawsze było to możliwe dla VB.net, ale jest teraz dostępne w języku C #.

Teraz nie musisz dodawać Add()metod bezpośrednio do swoich klas, możesz je zaimplementować jako metody rozszerzeń. Rozszerzając dowolny typ wyliczalny za pomocą Add()metody, będziesz mógł używać go w wyrażeniach inicjalizujących kolekcję. Więc nie musisz już czerpać z list wyraźnie ( jak wspomniano w innej odpowiedzi ), możesz po prostu go rozszerzyć.

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

To pozwoli ci to zrobić na dowolnej klasie, która implementuje 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 },
};

Oczywiście nie jesteś ograniczony do rozszerzania kolekcji krotek, może to być dla zbiorów dowolnego określonego typu, dla którego chcesz specjalnej składni.

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 będzie dodawać obsługę krotek wbudowanych w język, choć będą one innego typu ( System.ValueTuplezamiast tego). Dlatego dobrze byłoby dodać przeciążenia dla krotek wartości, abyś miał również możliwość ich użycia. Niestety nie ma między nimi żadnych domyślnych konwersji.

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

W ten sposób inicjalizacja listy będzie wyglądać jeszcze ładniej.

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

Ale zamiast przejść przez te wszystkie kłopoty, może być po prostu lepiej przejść na używanie ValueTuplewyłącznie.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};
Jeff Mercado
źródło
W końcu rzuciłem na to przyzwoite spojrzenie podczas aktualizacji mojej biblioteki do C # 6.0. Chociaż na pierwszy rzut oka wygląda dobrze, wolę moje wcześniej opublikowane rozwiązanie od tego, ponieważ wydaje mi TupleList<int, string>.się bardziej czytelne niż List<Tuple<int, string>>.
Steven Jeuris,
1
Jest to coś, co można naprawić alias typu. To prawda, że ​​sam alias nie może być ogólny, co może być lepsze. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado
Stworzyłem pakiet Nuget z metodami Add w ICollection, aby zaoszczędzić innym czasu na utrzymywanie tego kodu. Zobacz tutaj dla pakietu: nuget.org/packages/Naos.Recipes.TupleInitializers Zobacz tutaj kod: github.com/NaosProject/Naos.Recipes/blob/master/… To spowoduje dołączenie pliku cs do twojego rozwiązania pod „.Naos Folder .Recipes ”, więc nie musisz przeciągać zależności od złożenia
SFun28
42

Możesz to zrobić, wywołując konstruktor za każdym razem, gdy jest nieco lepszy

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
źródło
6
Nie mogłem uruchomić oryginalnego kodu, więc poprawiłem go do tego, co myślę, że powinien być ... co może odwrócić twoją opinię na temat tego, czy w końcu składnia „jest nieco lepsza”
dniu
5
przynajmniej użyj Tuple.Createzamiast tego i możesz wywnioskować argumenty typu
Dave Cousineau
28

Stare pytanie, ale zwykle tak robię, aby uczynić rzeczy nieco bardziej czytelnymi:

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
źródło
Działa również na starych wersjach .NET!
Ed Bayiates
1

Super Duper Old Wiem, ale chciałbym dodać swój artykuł na temat używania Linq i lambda kontynuacji na metodach przy użyciu C # 7. Próbuję używać nazwanych krotek jako zamienników DTO i anonimowych projekcji, gdy ponownie użyję ich w klasie. Tak, do wyśmiewania i testowania wciąż potrzebujesz klas, ale robienie rzeczy w linii i przekazywanie w klasie miło jest mieć tę nowszą opcję IMHO. Możesz utworzyć je z

  1. Bezpośrednia instancja
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Poza istniejącą kolekcją, a teraz możesz zwrócić dobrze napisane krotki podobne do tego, jak robiono anonimowe projekcje.
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. Ponownie użyj krotek później z metodami grupowania lub użyj metody do ich wbudowania w innej logice:
//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
źródło
Sprawiasz, że czuję się stary. :) Być może czegoś mi brakuje, ale inicjowanie „wstrzymań” wcale nie jest zwięzłe. To był właśnie punkt pytania.
Steven Jeuris,
@Steven Jeuris Nie, miałeś rację. Skopiowałem i wkleiłem niewłaściwy fragment kodu dla punktu 1. Musisz przejść trochę wolniej, zanim kliknę „prześlij”.
djangojazz,
0

Dlaczego lubisz krotki? To jak anonimowe typy: bez nazw. Nie rozumiem struktury danych.

Lubię klasyczne zajęcia

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" }
};
Aleksiej Obuchow
źródło
3
Jak powiedziałem: „Pozwalają ci szybko grupować istotne informacje bez konieczności pisania dla nich struktury lub klasy”. W przypadku, gdyby ta klasa była używana wyłącznie w ramach jednej metody jako struktury danych pomocnika, wolę używać Tuples, aby nie zapełniać przestrzeni nazw niepotrzebną klasą.
Steven Jeuris
1
Myślisz o starszej klasie Tuple (nie struct). C # 7 dodał krotki oparte na strukturze (wspierane przez nowy typ ValueTuple), które MOGĄ mieć nazwy, dzięki czemu można zrozumieć strukturę danych
Steve Niles,
0

Myślę, że jedna technika jest trochę łatwiejsza i nie została tu wcześniej wspomniana:

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

Myślę, że to trochę czystsze niż:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};
Alex Dresko
źródło
Sprowadza się to bardziej do gustu, czy wolisz wymieniać typy wyraźnie, czy nie, w tym samym próżności co do używania, varczy nie. Ja osobiście wolę pisanie jawne (gdy nie jest duplikowane np. W konstruktorze).
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
źródło
Jak uzyskać składnię wartość / nazwa za pomocą krotki?
Andreas Reiff
Kompilator informuje, że anonimowy typ nie jest domyślnie konwertowany na Tuple
cthepenier