Menu

Tuples en C# : tuples nommés, renvoyer plusieurs valeurs, déconstruction

Un tuple C# regroupe quelques valeurs en une seule sans déclarer de type. Apprenez la syntaxe des value tuples, les éléments nommés, renvoyer plusieurs valeurs depuis une méthode, déconstruire avec var (a, b), les clés tuple dans un dictionnaire, et ce qui distingue ValueTuple de l'ancien System.Tuple.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Un tuple regroupe un petit nombre fixe de valeurs en une seule valeur, sans déclarer de classe pour cela. ("Ana", 31) est un tuple composé d'une string et d'un int. Les tuples sont la façon idiomatique de renvoyer deux ou trois choses depuis une méthode et de conserver des paires éphémères à l'intérieur d'une méthode.

Créer un tuple

Le type s'écrit aussi avec des parenthèses : (string, int) est un type tuple dont le premier élément est une string et le second un int.

Sortie :

Ana is 31
(3, 4)
179.70
(Ana, 32)

Sans noms, les éléments s'appellent Item1, Item2, Item3, etc. Ces tuples sont des structs System.ValueTuple, donc affecter l'un à une autre variable copie les valeurs, et modifier la copie laisse l'original intact.

Éléments nommés

Item1 ne dit rien de ce qu'est la valeur. Donnez des noms aux éléments, dans le type ou dans le littéral :

Sortie :

Ana, 31
Desk lamp costs 24.99
Ana
Ana

Les noms sont une commodité de compilation. À l'exécution, les deux tuples ci-dessus sont de simples valeurs ValueTuple<string, int>, c'est pourquoi affecter person à un tuple aux noms différents compile : seuls les types des éléments et leur ordre doivent correspondre.

C# 7.1 et plus déduisent aussi les noms à partir des variables du littéral, si bien que var t = (name, age); vous donne t.name et t.age sans les écrire deux fois. En C# 7.0, ces éléments ne sont que Item1 et Item2.

Renvoyer plusieurs valeurs depuis une méthode

C'est la principale raison de l'ajout des tuples au langage. Avant eux, les options étaient les paramètres out, une classe ad hoc, ou System.Tuple avec son Item1 anonyme.

Sortie :

Lowest 60, highest 95
Average 78.75

Les noms d'éléments du type de retour deviennent ceux que voit l'appelant. Comparez avec la même méthode écrite avec des paramètres out :

static void Stats(int[] scores, out int min, out int max, out double average) { ... }

Stats(scores, out int min, out int max, out double avg);

Les paramètres out restent la convention pour la forme TryParse, où un bool indique si l'opération a réussi et où la valeur revient à côté. Pour « calculer plusieurs choses et les renvoyer », un tuple se lit mieux. Voir ref et out.

La déconstruction

La déconstruction décompose un tuple en variables séparées en une seule instruction :

Sortie :

Lima 24.5
Lima
24.5
2 1

Les noms de variables de var (city, temp) sont les vôtres ; ils n'ont pas à correspondre aux noms d'éléments du tuple, car la déconstruction se fait par position. L'échange (a, b) = (b, a) construit d'abord un tuple à partir des anciennes valeurs puis affecte, donc aucune valeur n'est perdue.

Vos propres classes peuvent prendre en charge la déconstruction en déclarant une méthode Deconstruct avec des paramètres out. Depuis C# 7 avec .NET Core 2.0 ou plus, KeyValuePair en a une aussi, donc une boucle sur un dictionnaire peut décomposer chaque entrée dans son en-tête :

class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

var (x, y) = new Point(3, 4);

foreach (var (name, score) in scoresByName)   // Dictionary<string, int>
{
    Console.WriteLine($"{name}: {score}");
}

La même forme foreach (var (a, b) in listOfTuples) fonctionne pour une liste de tuples. Prendre tout le tuple comme une seule variable de boucle et lire ses éléments nommés, comme le fait la section suivante, fonctionne tout aussi bien.

Les tuples dans les listes, LINQ et les dictionnaires

Une liste de tuples nommés est un moyen léger de garder des lignes de données dans une méthode :

Sortie :

Chloe 95
Ana 88
Ben 72
(Ana, B), (Chloe, A)
Ben
False

ValueTuple implémente Equals et GetHashCode élément par élément, donc (2, 7) construit à deux endroits différents trouve la même entrée de dictionnaire. Cela fait d'un tuple la clé composite la plus simple : pas de classe, pas de fonction de hachage écrite à la main, pas de concaténation de chaînes comme row + ":" + col.

Égalité de tuples

Equals compare élément par élément et fonctionne depuis l'introduction des tuples :

Sortie :

True
False

C# 7.3 a ajouté == et != pour les tuples, qui se compilent en la même comparaison élément par élément et ignorent aussi les noms :

if ((order.Status, order.Paid) == ("shipped", true)) { ... }
Console.WriteLine((1, "x") == (1, "x"));   // True

System.Tuple ou ValueTuple

.NET 4 a introduit System.Tuple, une classe créée avec Tuple.Create. Elle existe toujours dans le framework et apparaît encore dans du code et des API plus anciens, c'est pourquoi les recherches sur « tuple C# » tombent souvent dessus.

ValueTuple (C# 7)System.Tuple (.NET 4)
Syntaxe(1, "a"), (int, string)Tuple.Create(1, "a"), Tuple<int, string>
Naturestruct (type valeur)classe (type référence)
Noms d'élémentsOuiNon, seulement Item1, Item2
ModifiableOui, les éléments sont des champsNon, propriétés en lecture seule
DéconstructionOuiOui, via des méthodes d'extension

Sortie :

Ben 25
(Ben, 25)

ToValueTuple() et ToTuple() convertissent de l'un à l'autre. Préférez les value tuples dans le code nouveau : ils n'allouent rien sur le tas et peuvent porter des noms.

Quand utiliser plutôt une classe ou un record

Les tuples conviennent le mieux quand le regroupement est local et évident : une méthode renvoie deux nombres, une requête LINQ transmet une paire à l'étape suivante, un dictionnaire a besoin d'une clé en deux parties. Passez à un type nommé quand :

  • La même forme apparaît dans plusieurs signatures de méthodes publiques. (string, string, int) à cinq endroits est une classe qui n'a pas encore été écrite.
  • Les valeurs ont besoin de comportement, de validation, ou de plus de trois ou quatre éléments.
  • Les données franchissent une frontière comme la sérialisation. Les noms n'existent pas à l'exécution, donc Newtonsoft.Json écrit Item1 et Item2, et System.Text.Json écrit {}, car les éléments d'un tuple sont des champs, qu'il ignore par défaut.

Les records de C# 9 vous donnent l'essentiel de la commodité d'un tuple avec un vrai nom. record Score(string Name, int Points); a une égalité par valeur, un ToString lisible et la déconstruction intégrée. Voir records.

Erreurs courantes

  • S'attendre à ce que les noms survivent à l'exécution. La réflexion et les sérialiseurs voient Item1, Item2 (ou rien du tout).
  • Modifier une copie. Les tuples sont des structs : var t2 = t1; t2.Item1 = 5; laisse t1 inchangé.
  • Des tuples à nombreux éléments. Au-delà de trois ou quatre, un type nommé se lit mieux.
  • Confondre l'ordre de déconstruction. Il est positionnel : avec un tuple (Name, Age), var (age, name) = person; place le nom dans age, et comme var déduit les types, rien ne vous prévient.

Questions fréquentes

Comment renvoyer plusieurs valeurs depuis une méthode en C# ?

Renvoyez un tuple : déclarez le type de retour (int Min, int Max) et écrivez return (lowest, highest);. L'appelant lit result.Min et result.Max, ou déconstruit avec var (min, max) = MinMax(data);. Les alternatives sont les paramètres out, adaptés au schéma TryParse, ou une petite classe ou un record quand le résultat a un sens propre.

Qu'est-ce qu'un tuple nommé en C# ?

Un tuple dont les éléments ont des noms au lieu de Item1, Item2 : (string Name, int Age) person = ("Ana", 31); permet d'écrire person.Name. Les noms n'existent qu'à la compilation ; en dessous, c'est toujours un ValueTuple<string, int>, et Item1 continue de fonctionner.

Quelle est la différence entre Tuple et ValueTuple en C# ?

System.Tuple (depuis .NET 4) est une classe : allouée sur le tas, immuable, et ses éléments ne s'appellent jamais que Item1, Item2. System.ValueTuple (C# 7) est une struct aux champs modifiables, accepte les noms d'éléments et la syntaxe (a, b), et c'est ce que crée chaque littéral de tuple. Utilisez les value tuples dans le code nouveau.

Comment fonctionne la déconstruction de tuple en C# ?

var (name, age) = person; déclare deux variables et leur affecte les éléments du tuple dans l'ordre. Vous pouvez aussi affecter des variables existantes avec (a, b) = (b, a), ce qui les échange, et ignorer des éléments avec le discard _ : var (_, age) = person;.

Peut-on comparer des tuples avec == en C# ?

Depuis C# 7.3, == et != comparent les tuples élément par élément : (1, "a") == (1, "a") vaut true. Les noms sont ignorés ; seules les positions et les valeurs comptent. Avant C# 7.3, utilisez Equals, que les value tuples implémentent de la même façon.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER