Types Tuple (référence C#)

La fonctionnalité tuples fournit une syntaxe concise pour regrouper plusieurs éléments de données dans une structure de données légère. L’exemple suivant montre comment déclarer une variable tuple, l’initialiser et accéder à ses membres de données :

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

Comme l’illustre l’exemple précédent, pour définir un type tuple, vous spécifiez des types de tous ses membres de données et, éventuellement, les noms de champ. Vous ne pouvez pas définir de méthodes dans un type tuple, mais vous pouvez utiliser les méthodes fournies par .NET, comme l’illustre l’exemple suivant :

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

Les types Tuple prennent en charge les opérateurs d’égalité== et !=. Pour plus d’informations, consultez la section Égalité Tuple.

Les types tuple sont des types valeur ; les éléments tuples sont des champs publics. Cela fait des tuples des types de valeurs mutables.

Vous pouvez définir des tuples avec un grand nombre arbitraire d’éléments :

var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26);  // output: 26

Cas d’usage de tuples

L’un des cas d’usage les plus courants de tuples est en tant que type de retour de méthode. Autrement dit, au lieu de définir des paramètres de méthodeout, vous pouvez regrouper des résultats de méthode dans un type de retour tuple, comme l’illustre l’exemple suivant :

int[] xs = [4, 7, 9];
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9

int[] ys = [-9, 0, 67, 100];
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100

(int min, int max) FindMinMax(int[] input)
{
    if (input is null || input.Length == 0)
    {
        throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
    }

    // Initialize min to MaxValue so every value in the input
    // is less than this initial value.
    var min = int.MaxValue;
    // Initialize max to MinValue so every value in the input
    // is greater than this initial value.
    var max = int.MinValue;
    foreach (var i in input)
    {
        if (i < min)
        {
            min = i;
        }
        if (i > max)
        {
            max = i;
        }
    }
    return (min, max);
}

Comme l’illustre l’exemple précédent, vous pouvez utiliser l’instance de tuple retournée directement ou la déconstruire dans des variables distinctes.

Vous pouvez également utiliser des types tuples au lieu de types anonymes ; par exemple, dans les requêtes LINQ. Pour plus d’informations, consultez Choix entre les types anonymes et tuples.

En règle générale, vous utilisez des tuples pour regrouper des éléments de données faiblement associés. Dans les API publiques, envisagez de définir une classe ou un type de structure.

Noms de champs Tuple

Vous spécifiez explicitement les noms des champs tuples dans une expression d’initialisation de tuple ou dans la définition d’un type tuple, comme l’illustre l’exemple suivant :

var t = (Sum: 4.5, Count: 3);
Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");

(double Sum, int Count) d = (4.5, 3);
Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");

Si vous ne spécifiez pas de nom de champ, il peut être déduit du nom de la variable correspondante dans une expression d’initialisation de tuple, comme l’illustre l’exemple suivant :

var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");

C’est ce que l’on appelle des initialiseurs de projection tuple. Le nom d’une variable n’est pas projeté sur un nom de champ tuple dans les cas suivants :

  • Le nom du candidat est un nom de membre d’un type tuple, par exemple, Item3, ToStringou Rest.
  • Le nom du candidat est un doublon d’un autre nom de champ tuple, explicite ou implicite.

Dans les cas précédents, vous spécifiez explicitement le nom d’un champ ou accédez à un champ par son nom par défaut.

Les noms par défaut des champs tuple sont Item1, Item2, Item3, et ainsi de suite. Vous pouvez toujours utiliser le nom par défaut d’un champ, même lorsqu’un nom de champ est spécifié explicitement ou déduit, comme l’illustre l’exemple suivant :

var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.

L’affectation de tuple et les comparaisons d’égalité tuples ne prennent pas en compte les noms de champs.

Au moment de la compilation, le compilateur remplace les noms de champs qui ne sont pas par défaut par les noms par défaut correspondants. Par conséquent, les noms de champs explicitement spécifiés ou déduits ne sont pas disponibles au moment de l’exécution.

Conseil

Activez la règle de style de code .NET IDE0037 pour définir une préférence sur les noms de champs tuples déduits ou explicites.

À compter de C# 12, vous pouvez spécifier un alias pour un type tuple avec une using directive. L’exemple suivant ajoute un alias global using pour un type tuple avec deux valeurs entières pour une valeur Min et Max autorisée :

global using BandPass = (int Min, int Max);

Après avoir déclaré l’alias, vous pouvez utiliser le nom BandPass comme alias pour ce type de tuple :

BandPass bracket = (40, 100);
Console.WriteLine($"The bandpass filter is {bracket.Min} to {bracket.Max}");

Un alias n’introduit pas de nouveau type, mais crée uniquement un synonyme pour un type existant. Vous pouvez déconstruire un tuple déclaré avec l’alias BandPass comme vous pouvez le faire avec son type de tuple sous-jacent :

(int a , int b) = bracket;
Console.WriteLine($"The bracket is {a} to {b}");

Comme pour l’attribution ou la déconstruction de tuple, les noms de membres tuple n’ont pas besoin de correspondre ; les types le font.

De même, un deuxième alias avec le même arité et les mêmes types de membres peut être utilisé de manière interchangeable avec l’alias d’origine. Vous pouvez déclarer un deuxième alias :

using Range = (int Minimum, int Maximum);

Vous pouvez affecter un tuple Range à un tuple BandPass. Comme pour toutes les affectations de tuples, les noms de champs ne doivent pas nécessairement correspondre, uniquement les types et l’arité.

Range r = bracket;
Console.WriteLine($"The range is {r.Minimum} to {r.Maximum}");

Un alias pour un type tuple fournit plus d’informations sémantiques lorsque vous utilisez des tuples. Il n’introduit pas de nouveau type. Pour assurer la cohérence des types, vous devez plutôt déclarer une position record.

Affectation de tuple et déconstruction

Le langage C# prend en charge l’affectation entre les types tuples qui répondent aux deux conditions suivantes :

  • Les deux types tuple ont le même nombre d’éléments
  • Pour chaque position de tuple, le type de l’élément tuple droit est identique ou implicitement convertible au type de l’élément tuple gauche correspondant

Les valeurs d’élément Tuple sont attribuées en suivant l’ordre des éléments tuple. Les noms des champs tuples sont ignorés et non attribués, comme l’illustre l’exemple suivant :

(int, double) t1 = (17, 3.14);
(double First, double Second) t2 = (0.0, 1.0);
t2 = t1;
Console.WriteLine($"{nameof(t2)}: {t2.First} and {t2.Second}");
// Output:
// t2: 17 and 3.14

(double A, double B) t3 = (2.0, 3.0);
t3 = t2;
Console.WriteLine($"{nameof(t3)}: {t3.A} and {t3.B}");
// Output:
// t3: 17 and 3.14

Vous pouvez également utiliser l’opérateur d’affectation = pour déconstruire une instance tuple dans des variables distinctes. Vous pouvez le faire de plusieurs façons :

  • Utilisez le mot clé var en dehors des parenthèses pour déclarer des variables implicitement typées et laisser le compilateur déduire leurs types :

    var t = ("post office", 3.6);
    var (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Déclarez explicitement le type de chaque variable entre parenthèses :

    var t = ("post office", 3.6);
    (string destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Déclarez certains types explicitement et d’autres types implicitement (avec var) entre parenthèses :

    var t = ("post office", 3.6);
    (var destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Utilisez des variables existantes :

    var destination = string.Empty;
    var distance = 0.0;
    
    var t = ("post office", 3.6);
    (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    

La destination d’une expression de déconstruction peut inclure à la fois des variables existantes et des variables déclarées dans la déclaration de déconstruction.

Vous pouvez également combiner la déconstruction avec la correspondance de modèle pour inspecter les caractéristiques des champs d’un tuple. L’exemple suivant boucle plusieurs entiers et imprime ceux qui sont divisibles par 3. Il déconstruit le résultat du tuple de Int32.DivRem et correspond à un Remainder de 0 :

for (int i = 4; i < 20;  i++)
{
    if (Math.DivRem(i, 3) is ( Quotient: var q, Remainder: 0 ))
    {
        Console.WriteLine($"{i} is divisible by 3, with quotient {q}");
    }
}

Pour plus d’informations sur la déconstruction des tuples et autres types, consultez Déconstruction des tuples et autres types.

Égalité des tuples

Les types tuple prennent en charge les opérateurs == et !=. Ces opérateurs comparent les membres de l’opérande gauche aux membres correspondants de l’opérande droit en suivant l’ordre des éléments tuples.

(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right);  // output: True
Console.WriteLine(left != right);  // output: False

var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2);  // output: True
Console.WriteLine(t1 != t2);  // output: False

Comme l’illustre l’exemple précédent, les opérations == et != ne prennent pas en compte les noms de champs tuple.

Deux tuples sont comparables lorsque les deux conditions suivantes sont remplies :

  • Les deux tuples ont le même nombre d’éléments. Par exemple, t1 != t2 ne compile pas si t1 et t2 possèdent un nombre d’éléments différent.
  • Pour chaque position de tuple, les éléments correspondants des opérandes tuples gauche et droit sont comparables aux opérateurs == et !=. Par exemple, (1, (2, 3)) == ((1, 2), 3) ne compile pas car 1 n’est pas comparable à (1, 2).

Les opérateurs == et != comparent les tuples en court-circuit. Autrement dit, une opération s’arrête dès qu’elle rencontre une paire d’éléments inégaux ou atteint les extrémités des tuples. Toutefois, avant toute comparaison, tous les éléments de tuple sont évalués, comme l’illustre l’exemple suivant :

Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));

int Display(int s)
{
    Console.WriteLine(s);
    return s;
}
// Output:
// 1
// 2
// 3
// 4
// False

Les tuples en tant que paramètres sortants

En règle générale, vous refactorisez une méthode qui a des paramètres out dans une méthode qui retourne un tuple. Toutefois, il existe des cas dans lesquels un paramètre out peut être d’un type tuple. L’exemple suivant montre comment utiliser des tuples en tant que paramètres out :

var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))
{
    Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}
// Output:
// Found limits: min is 10, max is 20

Tuples vs System.Tuple

Les tuples C#, qui sont soutenus par des types System.ValueTuple, sont différents des tuples représentés par les types System.Tuple. Les principales différences sont les suivantes :

  • Les types System.ValueTuple sont des types valeur. Les types System.Tuple sont des types de référence.
  • Les types System.ValueTuple sont mutables. Les types System.Tuple sont immuables.
  • Les membres de données de type System.ValueTuple sont des champs. Les membres de données de type System.Tuple sont des propriétés.

spécification du langage C#

Pour plus d'informations, consultez les pages suivantes :

Voir aussi