Wie kann ich eine Liste von Tupeln einfach initialisieren?

286

Ich liebe Tupel . Mit ihnen können Sie relevante Informationen schnell gruppieren, ohne eine Struktur oder Klasse dafür schreiben zu müssen. Dies ist sehr nützlich, wenn Sie sehr lokalisierten Code umgestalten.

Das Initialisieren einer Liste von ihnen scheint jedoch etwas überflüssig.

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

Gibt es keinen besseren Weg? Ich würde eine Lösung nach dem Vorbild des Dictionary-Initialisierers lieben .

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

Können wir keine ähnliche Syntax verwenden?

Steven Jeuris
quelle
2
Warum würden Sie in diesem Fall kein Wörterbuch anstelle einer Liste von Tupeln verwenden?
Ed S.
17
@Ed S.: A Dictionaryerlaubt keine doppelten Schlüssel.
Steven Jeuris
2
@EdS.: Jedes Mal ist es kein Zwei-Tupel, bei dem ein Artikel hashbar / bestellbar und einzigartig ist.
Guter Punkt, habe den doppelten Schlüssel nicht bemerkt.
Ed S.

Antworten:

279

Mit c # 7.0 können Sie Folgendes tun:

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

Wenn Sie kein List, sondern nur ein Array benötigen , können Sie Folgendes tun:

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

Und wenn Sie "Item1" und "Item2" nicht mögen, können Sie Folgendes tun:

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

oder für ein Array:

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

Damit können Sie Folgendes tun: tupleList[0].IndexundtupleList[0].Name

Framework 4.6.2 und niedriger

Sie müssen System.ValueTupleüber den Nuget Package Manager installieren .

Framework 4.7 und höher

Es ist in das Framework eingebaut. Sie nicht installieren System.ValueTuple. Entfernen Sie es tatsächlich und löschen Sie es aus dem bin-Verzeichnis.

Hinweis: Im wirklichen Leben könnte ich nicht zwischen Kuh, Hühnern oder Flugzeug wählen. Ich wäre wirklich hin und her gerissen.

toddmo
quelle
2
Kann dies auf einem .net Core 2.0 verwendet werden?
Алекса Јевтић
2
@ АлексаЈевтић, System.ValueTupleunterstützt Core 2.0. Versuchen Sie es jedoch zuerst ohne Nuget, da unnötige Pakete Probleme verursachen können. Also so oder so, ja. Verwenden Sie nach Möglichkeit c # v7 oder höher.
Toddmo
1
Absolut perfekte Antwort! Vielen Dank!
Vitox
224

Ja! Das ist möglich .

Die {} -Syntax des Auflistungsinitialisierers funktioniert für jeden IEnumerable- Typ, der über eine Add- Methode mit der richtigen Anzahl von Argumenten verfügt. Ohne sich darum zu kümmern, wie das unter der Decke funktioniert, können Sie einfach von Liste <T> aus erweitern , eine benutzerdefinierte Add-Methode hinzufügen, um Ihr T zu initialisieren , und fertig!

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

Auf diese Weise können Sie Folgendes tun:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};
Steven Jeuris
quelle
39
Ein Nachteil ist, die Klasse schreiben zu müssen. Wie Sie liebe ich Tupel, weil ich keine Klasse oder Struktur schreiben muss.
Goodeye
2
@ BillW Ich bin mir nicht sicher, ob dies der genaue Beitrag ist, aber ich weiß, dass Jon Skeet schon einmal darüber geschrieben hat .
Steven Jeuris
1
funktioniert das groceryList[0] == groceryList[1]oder muss ein vergleich durchgeführt werden?
Byyo
Ich habe ein Nuget-Paket mit Add-Methoden in ICollection erstellt, um anderen die Zeit zu ersparen, diesen Code warten zu müssen. Paket finden Sie hier : nuget.org/packages/Naos.Recipes.TupleInitializers Code finden Sie hier: github.com/NaosProject/Naos.Recipes/blob/master/… Dies enthält eine CS-Datei in Ihrer Lösung unter einem ".Naos" .Recipes "Ordner, so dass Sie keine Assembly-Abhängigkeit ziehen müssen
SFun28
83

C # 6 fügt nur hierfür eine neue Funktion hinzu: Erweiterung Methoden hinzufügen. Dies war für VB.net immer möglich, ist aber jetzt in C # verfügbar.

Jetzt müssen Sie Add()Ihren Klassen keine Methoden mehr direkt hinzufügen, sondern können sie als Erweiterungsmethoden implementieren. Wenn Sie einen Aufzählungstyp mit einer Add()Methode erweitern, können Sie ihn in Initialisierungsausdrücken für Sammlungen verwenden. Sie müssen also nicht mehr explizit von Listen ableiten ( wie in einer anderen Antwort erwähnt ), sondern können diese einfach erweitern.

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

Auf diese Weise können Sie dies für jede Klasse tun, die Folgendes implementiert 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 },
};

Natürlich können Sie nicht nur Tupel-Sammlungen erweitern, sondern auch Sammlungen eines bestimmten Typs, für den Sie die spezielle Syntax wünschen.

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 wird die Unterstützung für in die Sprache integrierte Tupel hinzufügen, obwohl sie ( System.ValueTuplestattdessen) von einem anderen Typ sind . Es wäre also gut, Überladungen für Wertetupel hinzuzufügen, damit Sie die Option haben, diese auch zu verwenden. Leider sind keine impliziten Konvertierungen zwischen den beiden definiert.

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

Auf diese Weise sieht die Listeninitialisierung noch besser aus.

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

Aber anstatt all diese Probleme zu lösen, ist es möglicherweise besser, auf die ValueTupleausschließliche Verwendung umzusteigen.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};
Jeff Mercado
quelle
Ich habe mir das endlich genauer angesehen, als ich meine Bibliothek auf C # 6.0 aktualisiert habe. Obwohl es auf den ersten Blick gut aussieht, ziehe ich meine zuvor veröffentlichte Lösung dieser vor, weil ich finde TupleList<int, string>., dass sie besser lesbar ist als List<Tuple<int, string>>.
Steven Jeuris
1
Das kann ein Typ-Alias ​​beheben. Zugegeben, der Alias ​​selbst kann nicht generisch sein, was vorzuziehen ist. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado
Ich habe ein Nuget-Paket mit Add-Methoden in ICollection erstellt, um anderen die Zeit zu ersparen, diesen Code warten zu müssen. Paket finden Sie hier : nuget.org/packages/Naos.Recipes.TupleInitializers Code finden Sie hier: github.com/NaosProject/Naos.Recipes/blob/master/… Dies enthält eine CS-Datei in Ihrer Lösung unter einem ".Naos" Ordner ".Recipes", damit Sie keine Assembly-Abhängigkeit verschieben müssen
SFun28
42

Sie können dies tun, indem Sie den Konstruktor jedes Mal aufrufen, wenn dies etwas besser ist

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
quelle
6
Ich konnte den ursprünglichen Code nicht zum Laufen bringen, also habe ich ihn so geändert, wie ich denke, dass er sein sollte ... was Ihre Meinung darüber umkehren könnte, ob die Syntax doch "etwas besser" ist :)
am
5
Verwenden Sie Tuple.Createstattdessen stattdessen und Sie können die Typargumente ableiten
Dave Cousineau
28

Alte Frage, aber das ist es, was ich normalerweise mache, um die Dinge ein bisschen lesbarer zu machen:

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
quelle
Funktioniert auch mit alten Versionen von .NET!
Ed Bayiates
1

Super Duper Old Ich weiß, aber ich würde mein Stück über die Verwendung von Linq und Continuation Lambdas zu Methoden mit C # 7 hinzufügen. Ich versuche, benannte Tupel als Ersatz für DTOs und anonyme Projektionen zu verwenden, wenn sie in einer Klasse wiederverwendet werden. Ja, zum Verspotten und Testen benötigen Sie noch Klassen, aber es ist schön, diese neuere Option IMHO zu haben, wenn Sie Dinge inline erledigen und in einer Klasse herumreichen. Sie können sie von instanziieren

  1. Direkte Instanziierung
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Aus einer vorhandenen Sammlung, und jetzt können Sie gut getippte Tupel zurückgeben, ähnlich wie früher anonyme Projektionen.
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. Verwenden Sie die Tupel später erneut mit Gruppierungsmethoden oder verwenden Sie eine Methode, um sie in einer anderen Logik inline zu erstellen:
//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
quelle
Du lässt mich alt werden. :) Ich vermisse hier vielleicht etwas, aber das Initialisieren von 'Holds' ist überhaupt nicht prägnant. Dies war der genaue Punkt der Frage.
Steven Jeuris
@Steven Jeuris Nein, Sie hatten Recht. Ich habe den falschen Code für Punkt 1 kopiert und eingefügt. Ich muss etwas langsamer fahren, bevor ich auf "Senden" klicke.
Djangojazz
0

Warum magst du Tupel? Es ist wie bei anonymen Typen: keine Namen. Kann die Struktur von Daten nicht verstehen.

Ich mag klassische Klassen

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
quelle
3
Wie ich bereits sagte: "Sie ermöglichen es Ihnen, relevante Informationen schnell zu gruppieren, ohne eine Struktur oder Klasse dafür schreiben zu müssen." Für den Fall, dass diese Klasse nur innerhalb einer einzelnen Methode als Hilfsdatenstruktur verwendet wird, bevorzuge ich die Verwendung von Tupeln, um einen Namespace nicht mit einer unnötigen Klasse zu füllen.
Steven Jeuris
1
Sie denken an die ältere Tuple-Klasse (nicht struct). C # 7 fügte strukturbasierte Tupel hinzu (unterstützt durch den neuen ValueTuple-Typ), die Namen haben KÖNNEN, damit Sie die Struktur von Daten verstehen können
Steve Niles
0

Eine Technik, die ich für etwas einfacher halte und die hier noch nicht erwähnt wurde:

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

Ich finde das etwas sauberer als:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};
Alex Dresko
quelle
Das hängt mehr vom Geschmack ab, ob Sie es vorziehen, Typen explizit zu erwähnen oder nicht, genauso vergeblich wie zu verwenden varoder nicht. Ich persönlich bevorzuge die explizite Eingabe (wenn sie nicht zB im Konstruktor dupliziert wird).
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
quelle
Wie erhält man die Wert- / Namenssyntax mit einem Tupel?
Andreas Reiff
Der Compiler teilt mit, dass anonyme Typen nicht implizit in
Tuple