Comment initialiser facilement une liste de Tuples?

287

J'adore les tuples . Ils vous permettent de regrouper rapidement les informations pertinentes sans avoir à écrire une structure ou une classe pour cela. Ceci est très utile lors de la refactorisation de code très localisé.

L'initialisation d'une liste semble cependant un peu redondante.

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

N'y a-t-il pas une meilleure façon? J'adorerais une solution sur le modèle de l' initialiseur de dictionnaire .

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

Ne pouvons-nous pas utiliser une syntaxe similaire?

Steven Jeuris
la source
2
Dans ce cas, pourquoi n'utiliseriez- vous pas un dictionnaire au lieu d'une liste de Tuples?
Ed S.
17
@Ed S .: A Dictionaryn'autorise pas les clés en double.
Steven Jeuris
2
@EdS.: Chaque fois, ce n'est pas un tuple où un article est lavable / commandable et unique.
Bon point, je n'ai pas remarqué la clé en double.
Ed S.

Réponses:

282

c # 7.0 vous permet de faire ceci:

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

Si vous n'avez pas besoin d'un List, mais simplement d'un tableau, vous pouvez faire:

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

Et si vous n'aimez pas "Item1" et "Item2", vous pouvez faire:

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

ou pour un tableau:

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

qui vous permet de faire: tupleList[0].IndexettupleList[0].Name

Framework 4.6.2 et inférieur

Vous devez installer à System.ValueTuplepartir du gestionnaire de packages Nuget.

Framework 4.7 et supérieur

Il est intégré dans le cadre. Ne pas installer System.ValueTuple. En fait, supprimez-le et supprimez-le du répertoire bin.

note: dans la vraie vie, je ne pourrais pas choisir entre vache, poulets ou avion. Je serais vraiment déchiré.

toddmo
la source
2
Peut-il être utilisé sur un noyau .net 2.0?
Алекса Јевтић
2
@ АлексаЈевтић, System.ValueTupleprend en charge le noyau 2.0. Mais essayez-le sans Nuget d'abord, car les packages inutiles peuvent causer des problèmes. Donc, d'une manière ou d'une autre, oui. Utilisez c # v7 ou supérieur si possible.
toddmo
1
Réponse absolument parfaite! Merci!
Vitox
224

Oui! C'est possible .

La syntaxe {} de l'initialiseur de collection fonctionne sur tout type IEnumerable qui a une méthode Add avec la quantité correcte d'arguments. Sans se soucier de la façon dont cela fonctionne sous les couvertures, cela signifie que vous pouvez simplement étendre à partir de List <T> , ajouter une méthode Add personnalisée pour initialiser votre T , et vous avez terminé!

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

Cela vous permet d'effectuer les opérations suivantes:

var groceryList = new TupleList<int, string>
{
    { 1, "kiwi" },
    { 5, "apples" },
    { 3, "potatoes" },
    { 1, "tomato" }
};
Steven Jeuris
la source
39
Un inconvénient est d'avoir à écrire la classe. Comme vous, j'adore les tuples car je n'ai pas à écrire de classe ou de structure.
goodeye
2
@BillW Pas trop sûr que ce soit le message exact, ... mais je sais que Jon Skeet a déjà écrit à ce sujet .
Steven Jeuris
1
cela fonctionne groceryList[0] == groceryList[1]-t-il ou faut-il effectuer une comparaison?
Byyo
J'ai créé un package Nuget avec des méthodes d'ajout sur ICollection pour économiser aux autres le temps d'avoir à maintenir ce code. Voir ici pour le paquet: nuget.org/packages/Naos.Recipes.TupleInitializers Voir ici pour le code: github.com/NaosProject/Naos.Recipes/blob/master/… Cela inclura un fichier cs dans votre solution sous un ".Naos .Recipes ", pour que vous n'ayez pas à déplacer une dépendance d'assembly
SFun28
83

C # 6 ajoute une nouvelle fonctionnalité juste pour cela: extension Ajouter des méthodes. Cela a toujours été possible pour VB.net mais est maintenant disponible en C #.

Maintenant, vous n'avez plus besoin d'ajouter Add()directement des méthodes à vos classes, vous pouvez les implémenter en tant que méthodes d'extension. Lorsque vous étendez un type énumérable avec une Add()méthode, vous pourrez l'utiliser dans des expressions d'initialisation de collection. Vous n'avez donc plus besoin de dériver de listes explicitement ( comme mentionné dans une autre réponse ), vous pouvez simplement l'étendre.

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

Cela vous permettra de le faire sur n'importe quelle classe qui implémente 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 },
};

Bien sûr, vous n'êtes pas limité à l'extension des collections de tuples, cela peut être pour les collections de tout type spécifique pour lequel vous voulez la syntaxe spéciale.

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 ajoutera le support des tuples intégrés dans le langage, bien qu'ils soient d'un type différent (à la System.ValueTupleplace). Il serait donc bon d'ajouter des surcharges pour les tuples de valeur afin que vous ayez la possibilité de les utiliser également. Malheureusement, aucune conversion implicite n'est définie entre les deux.

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

De cette façon, l'initialisation de la liste sera encore plus agréable.

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

Mais au lieu de passer par tous ces problèmes, il serait peut-être préférable de passer à l'utilisation ValueTupleexclusive.

var points = new List<(int, int, int)>
{
    (0, 0, 0),
    (1, 2, 3),
    (-1, 12, -73),
};
Jeff Mercado
la source
J'ai finalement jeté un œil décent à cela lors de la mise à jour de ma bibliothèque vers C # 6.0. Bien que cela semble bon en un coup d'œil, je préfère ma solution précédemment publiée à cela car je trouve TupleList<int, string>.qu'elle est plus lisible que List<Tuple<int, string>>.
Steven Jeuris
1
C'est quelque chose qu'un alias de type peut résoudre. Certes, l'alias lui-même ne peut pas être générique, ce qui peut être préférable. using TupleList = System.Collections.Generic.List<System.Tuple<int, string>>;
Jeff Mercado
J'ai créé un package Nuget avec des méthodes d'ajout sur ICollection pour économiser aux autres le temps d'avoir à maintenir ce code. Voir ici pour le paquet: nuget.org/packages/Naos.Recipes.TupleInitializers Voir ici pour le code: github.com/NaosProject/Naos.Recipes/blob/master/… Cela inclura un fichier cs dans votre solution sous un ".Naos .Recipes ", afin que vous n'ayez pas à déplacer une dépendance d'assembly
SFun28
42

Vous pouvez le faire en appelant le constructeur à chaque fois avec est légèrement meilleur

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
la source
6
Je n'ai pas réussi à faire fonctionner le code d'origine, donc je l'ai modifié pour qu'il soit ce que je pense qu'il devrait être ... ce qui peut inverser votre opinion sur la question de savoir si la syntaxe "est légèrement meilleure" après tout :)
onedaywhen
5
utilisez au moins à la Tuple.Createplace et vous pouvez déduire les arguments de type
Dave Cousineau
28

Vieille question, mais c'est ce que je fais généralement pour rendre les choses un peu plus lisibles:

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
la source
Fonctionne également sur les anciennes versions de .NET!
Ed Bayiates
1

Super Duper Old, je sais, mais j'ajouterais mon article sur l'utilisation de Linq et les lambdas de continuation sur les méthodes utilisant C # 7. J'essaie d'utiliser des tuples nommés en remplacement des DTO et des projections anonymes lorsqu'ils sont réutilisés dans une classe. Oui pour se moquer et tester, vous avez toujours besoin de classes, mais faire les choses en ligne et passer dans une classe est bien d'avoir cette nouvelle option à mon humble avis. Vous pouvez les instancier à partir de

  1. Instanciation directe
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
  1. Hors d'une collection existante, et maintenant vous pouvez renvoyer des tuples bien typés similaires à la façon dont les projections anonymes étaient effectuées.
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. Réutilisez les tuples plus tard avec des méthodes de regroupement ou utilisez une méthode pour les construire en ligne dans une autre logique:
//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
la source
Tu me fais me sentir vieux. :) Je manque peut-être quelque chose ici, mais l'initialisation de «prises» n'est pas du tout concise. C'était le point exact de la question.
Steven Jeuris
@Steven Jeuris Non, vous aviez raison J'ai copié et collé le mauvais morceau de code pour le point 1. Besoin d'aller un peu plus lentement avant de cliquer sur «soumettre».
djangojazz
0

Pourquoi aimez-vous les tuples? C'est comme des types anonymes: pas de noms. Impossible de comprendre la structure des données.

J'aime les cours classiques

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
la source
3
Comme je l'ai dit: "Ils vous permettent de regrouper rapidement les informations pertinentes sans avoir à écrire une structure ou une classe pour cela." Dans le cas où cette classe serait uniquement utilisée dans une seule méthode en tant que structure de données auxiliaire, je préfère utiliser Tuples, afin de ne pas remplir un espace de noms avec une classe inutile.
Steven Jeuris
1
Vous pensez à l'ancienne classe Tuple (pas struct). C # 7 a ajouté des tuples basés sur des structures (soutenus par le nouveau type ValueTuple) qui PEUVENT avoir des noms, afin que vous puissiez comprendre la structure des données
Steve Niles
0

Une technique que je pense est un peu plus facile et qui n'a pas été mentionnée avant ici:

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

Je pense que c'est un peu plus propre que:

var asdf = new List<Tuple<int, string>> { 
    (Age: 1, Name: "cow"), 
    (Age: 2, Name: "bird")
};
Alex Dresko
la source
Cela revient plus à goûter si vous préférez mentionner explicitement ou non les types, dans le même vain que d'utiliser varou non. Personnellement, je préfère la frappe explicite (lorsqu'elle n'est pas dupliquée, par exemple dans le constructeur).
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
la source
Comment obtenir la syntaxe valeur / nom avec un tuple?
Andreas Reiff,
Le compilateur indique que les types anonymes ne sont pas implicitement convertibles en
Tuple