Types tuple C#C# tuple types

Les tuples C# sont des types que vous définissez à l’aide d’une syntaxe simplifiée.C# tuples are types that you define using a lightweight syntax. Les avantages incluent une syntaxe simplifiée, des règles de conversion basées sur le nombre (appelé « cardinalité ») et les types d’éléments, ainsi que des règles cohérentes pour les copies, les tests d’égalité et les affectations.The advantages include a simpler syntax, rules for conversions based on number (referred to as cardinality) and types of elements, and consistent rules for copies, equality tests, and assignments. En échange, les tuples ne prennent pas en charge certains idiomes orientés objet associés à l’héritage.As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance. Vous pouvez bénéficier d’une présentation des tuples dans la section correspondante de l’article Nouveautés de C# 7.0.You can get an overview in the section on tuples in the What's new in C# 7.0 article.

Dans cet article, vous allez apprendre les règles de langage régissant les tuples dans C# 7.0 et ultérieur, découvrir différentes façons de les utiliser et bénéficier de conseils de base sur l’utilisation des tuples.In this article, you'll learn the language rules governing tuples in C# 7.0 and later versions, different ways to use them, and initial guidance on working with tuples.

Notes

Les nouvelles fonctionnalités des tuples exigent les types ValueTuple.The new tuples features require the ValueTuple types. Vous devez ajouter le package NuGet System.ValueTuple pour pouvoir l’utiliser sur les plateformes qui n’incluent pas les types.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

Ces fonctionnalités sont semblables à celles d’autres langages qui reposent sur les types fournis dans le framework.This is similar to other language features that rely on types delivered in the framework. async et await qui reposent sur l’interface INotifyCompletion, et LINQ qui repose sur IEnumerable<T> en sont des exemples.Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. Toutefois, le mécanisme de remise change à mesure que le .NET dépend de moins en moins de la plateforme.However, the delivery mechanism is changing as .NET is becoming more platform independent. Le .NET Framework n’est pas toujours émis à la même cadence que le compilateur de langage.The .NET Framework may not always ship on the same cadence as the language compiler. Quand les nouvelles fonctionnalités de langage reposent sur de nouveaux types, ces types sont disponibles sous la forme de packages NuGet au moment de l’émission des fonctionnalités de langage.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. À mesure que ces nouveaux types sont ajoutés à l’API .NET Standard et remis dans le cadre du framework, les packages NuGet ne sont plus obligatoires.As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

Commençons par passer en revue les raisons d’ajouter la nouvelle prise en charge des tuples.Let's start with the reasons for adding new tuple support. Les méthodes retournent un objet unique.Methods return a single object. Les tuples vous permettent d’empaqueter plus aisément plusieurs valeurs dans cet objet unique.Tuples enable you to package multiple values in that single object more easily.

Le .NET Framework possède déjà des classes Tuple génériques.The .NET Framework already has generic Tuple classes. Ces classes, toutefois, présentaient deux limitations majeures.These classes, however, had two major limitations. Premièrement, les classes Tuple nommaient leurs propriétés Item1, Item2, etc.For one, the Tuple classes named their properties Item1, Item2, and so on. Ces noms ne comportent aucune information sémantique.Those names carry no semantic information. L’utilisation de ces types Tuple ne permet pas de communiquer la signification de chacune de ces propriétés.Using these Tuple types does not enable communicating the meaning of each of the properties. Les nouvelles fonctionnalités de langage vous permettent de déclarer et d’utiliser des noms sémantiquement explicites pour les éléments d’un tuple.The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

Les classes Tuple entraînent des problèmes de performances car elles sont des types référence.The Tuple classes cause more performance concerns because they are reference types. Utiliser un des types Tuple signifie allouer des objets.Using one of the Tuple types means allocating objects. Sur des chemins réactifs, l’allocation de nombreux petits objets peut avoir un impact mesurable sur les performances de votre application.On hot paths, allocating many small objects can have a measurable impact on your application's performance. Par conséquent, la prise en charge du langage pour les tuples tire parti des nouveaux structs ValueTuple.Therefore, the language support for tuples leverages the new ValueTuple structs.

Pour éviter ces faiblesses, vous pouvez créer une class ou un struct pour fournir plusieurs éléments.To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Malheureusement, cela génère un surplus de travail et masque votre intention de conception.Unfortunately, that's more work for you, and it obscures your design intent. La création d’un struct ou d’une class implique la définition d’un type avec des données et un comportement.Making a struct or class implies that you are defining a type with both data and behavior. Souvent, vous souhaitez simplement stocker plusieurs valeurs dans un objet unique.Many times, you simply want to store multiple values in a single object.

Les fonctionnalités du langage et les structs génériques ValueTuple appliquent la règle stipulant que vous ne pouvez pas ajouter de comportement (méthodes) à ces types tuple.The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. Tous les types ValueTuple sont des structs mutables.All the ValueTuple types are mutable structs. Chaque champ de membre est un champ public.Each member field is a public field. Cela les rend très légers.That makes them very lightweight. Toutefois, cela signifie que les tuples ne doivent pas être utilisés quand l’immuabilité est importante.However, that means tuples should not be used where immutability is important.

Les tuples sont des conteneurs de données plus simples et plus flexibles que les types class et struct.Tuples are both simpler and more flexible data containers than class and struct types. Examinons ces différences.Let's explore those differences.

Tuples nommés et sans nomNamed and unnamed tuples

Le struct ValueTuple possède des champs nommés Item1, Item2, Item3, etc., similaires aux propriétés définies dans les types Tuple existants.The ValueTuple struct has fields named Item1, Item2, Item3, and so on, similar to the properties defined in the existing Tuple types. Ces noms sont les seuls noms que vous pouvez utiliser pour les tuples sans nom.These names are the only names you can use for unnamed tuples. Quand vous ne fournissez pas de nom de champ alternatif à un tuple, vous avez créé un tuple sans nom :When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

var unnamed = ("one", "two");

Le tuple dans l’exemple précédent a été initialisé à l’aide de constantes littérales et n’a pas de noms d’élément créés avec les projections de nom de champ de tuple dans C# 7.1.The tuple in the previous example was initialized using literal constants and won't have element names created using tuple field name projections in C# 7.1.

Toutefois, quand vous initialisez un tuple, vous pouvez utiliser les nouvelles fonctionnalités de langage qui donnent de meilleurs noms aux différents champs.However, when you initialize a tuple, you can use new language features that give better names to each field. Cette opération crée un tuple nommé.Doing so creates a named tuple. Les tuples nommés ont toujours des éléments appelés Item1, Item2, Item3, etc.Named tuples still have elements named Item1, Item2, Item3 and so on. Cependant, ils ont également des synonymes pour tous les éléments que vous avez nommés.But they also have synonyms for any of those elements that you have named. Vous créez un tuple nommé en spécifiant le nom de chaque élément.You create a named tuple by specifying the names for each element. Une méthode consiste à spécifier les noms dans le cadre de l’initialisation du tuple :One way is to specify the names as part of the tuple initialization:

var named = (first: "one", second: "two");

Ces synonymes sont gérés par le compilateur et le langage pour vous permettre d’utiliser efficacement les tuples nommés.These synonyms are handled by the compiler and the language so that you can use named tuples effectively. Les IDE et les éditeurs peuvent lire ces noms sémantiques à l’aide des API Roslyn.IDEs and editors can read these semantic names using the Roslyn APIs. Vous pouvez référencer les éléments d’un tuple nommé par ces noms sémantiques n’importe où dans le même assembly.You can reference the elements of a named tuple by those semantic names anywhere in the same assembly. Le compilateur remplace les noms que vous avez définis par les équivalents Item* lors de la génération de la sortie compilée.The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. Le langage MSIL (Microsoft Intermediate Language) compilé n’inclut pas les noms que vous avez donnés à ces éléments.The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

À partir de C# 7.1, les noms de champ d’un tuple peuvent être fournis à partir de variables utilisées pour initialiser le tuple.Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. Ils sont appelés initialiseurs de projection de tuple.This is referred to as tuple projection initializers. Le code suivant crée un tuple nommé accumulation avec les éléments count (un entier) et sum (un double).The following code creates a tuple named accumulation with elements count (an integer), and sum (a double).

var sum = 12.5;
var count = 5;
var accumulation = (count, sum);

Le compilateur doit communiquer les noms que vous avez créés pour les tuples qui sont retournés à partir des propriétés et méthodes publiques.The compiler must communicate those names you created for tuples that are returned from public methods or properties. Dans ces cas, le compilateur ajoute un attribut TupleElementNamesAttribute sur la méthode.In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. Cet attribut contient une propriété de liste TransformNames qui contient les noms attribués à chacun des éléments du tuple.This attribute contains a TransformNames list property that contains the names given to each of the elements in the tuple.

Notes

Les outils de développement, tels que Visual Studio, lisent également ces métadonnées et fournissent IntelliSense et d’autres fonctionnalités qui utilisent les noms des champs de métadonnées.Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

Il est important de comprendre les notions de base sous-jacentes des nouveaux tuples et du type ValueTuple afin de comprendre les règles d’affectation des tuples nommés entre eux.It is important to understand these underlying fundamentals of the new tuples and the ValueTuple type in order to understand the rules for assigning named tuples to each other.

Initialiseurs de projection de tupleTuple projection initializers

En général, les initialiseurs de projection de tuple fonctionnent en utilisant les noms de champ ou de variable de la partie droite d’une instruction d’initialisation de tuple.In general, tuple projection initializers work by using the variable or field names from the right-hand side of a tuple initialization statement. Si un nom explicite est fourni, il est prioritaire sur n’importe quel nom projeté.If an explicit name is given, that takes precedence over any projected name. Par exemple, dans l’initialiseur suivant, les éléments sont explicitFieldOne et explicitFieldTwo, et non localVariableOne et localVariableTwo:For example, in the following initializer, the elements are explicitFieldOne and explicitFieldTwo, not localVariableOne and localVariableTwo:

var localVariableOne = 5;
var localVariableTwo = "some text";

var tuple = (explicitFieldOne: localVariableOne, explicitFieldTwo: localVariableTwo);

Pour tous les champs où un nom explicite n’est pas spécifié, un nom implicite applicable est projeté.For any field where an explicit name is not provided, an applicable implicit name is projected. Il n’existe aucune obligation de fournir des noms sémantiques, explicitement ou implicitement.There is no requirement to provide semantic names, either explicitly or implicitly. L’initialiseur suivant a des noms de champ Item1 dont la valeur est 42, et stringContent dont la valeur est « The answer to everything » :The following initializer has field names Item1, whose value is 42 and stringContent, whose value is "The answer to everything":

var stringContent = "The answer to everything";
var mixedTuple = (42, stringContent);

Il existe deux conditions où les noms de champ de candidat ne sont pas projetés sur le champ de tuple :There are two conditions where candidate field names are not projected onto the tuple field:

  1. Lorsque le nom du candidat est un nom de tuple réservé.When the candidate name is a reserved tuple name. Par exemple Item3, ToStringExamples include Item3, ToString. ou Rest.or Rest.
  2. Lorsque le nom du candidat est un doublon d’un autre nom de champ de tuple, explicite ou implicite.When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

Ces conditions évitent toute ambiguïté.These conditions avoid ambiguity. Ces noms provoqueraient une ambiguïté s’ils étaient utilisés comme noms de champ pour un champ dans un tuple.These names would cause an ambiguity if they were used as the field names for a field in a tuple. Aucune de ces conditions n’entraîne d’erreur au moment de la compilation.Neither of these conditions cause compile-time errors. Au lieu de cela, les éléments sans noms projetés n’ont pas de noms sémantiques projetés.Instead, the elements without projected names do not have semantic names projected for them. Les exemples suivants illustrent ces conditions :The following examples demonstrate these conditions:

var ToString = "This is some text";
var one = 1;
var Item1 = 5;
var projections = (ToString, one, Item1);
// Accessing the first field:
Console.WriteLine(projections.Item1);
// There is no semantic name 'ToString'
// Accessing the second field:
Console.WriteLine(projections.one);
Console.WriteLine(projections.Item2);
// Accessing the third field:
Console.WriteLine(projections.Item3);
// There is no semantic name 'Item1`.

var pt1 = (X: 3, Y: 0);
var pt2 = (X: 3, Y: 4);

var xCoords = (pt1.X, pt2.X);
// There are no semantic names for the fields
// of xCoords. 

// Accessing the first field:
Console.WriteLine(xCoords.Item1);
// Accessing the second field:
Console.WriteLine(xCoords.Item2);

Ces cas de figure n’entraînent aucune erreur du compilateur, car ce serait une modification avec rupture du code écrit en C# 7.0 si les projections de nom de champ de tuple n’étaient pas disponibles.These situations do not cause compiler errors because that would be a breaking change for code written with C# 7.0, when tuple field name projections were not available.

Égalité et tuplesEquality and tuples

À compter de C# 7.3, les types tuple prennent en charge les opérateurs == et !=.Beginning with C# 7.3, tuple types support the == and != operators. Ces opérateurs fonctionnent en comparant, dans l’ordre, chaque membre de l’argument de gauche à chaque membre de l’argument de droite.These operators work by comparing each member of the left argument to each member of the right argument in order. Ces comparaisons se court-circuitent.These comparisons short-circuit. Elles arrêtent l’évaluation des membres dès qu’une paire n’est pas égale.They will stop evaluating members as soon as one pair is not equal. Les exemples de code suivants utilisent ==, mais toutes les règles de comparaison s’appliquent à !=.The following code examples use ==, but the comparison rules all apply to !=. L’exemple de code suivant montre une comparaison d’égalité pour deux paires d’entiers :The following code example shows an equality comparison for two pairs of integers:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'

Il existe plusieurs règles qui rendent les tests d’égalité de tuple plus pratiques.There are several rules that make tuple equality tests more convenient. L’égalité de tuple effectue des conversions de type «lifted» si un des tuples est un tuple nullable, comme indiqué dans le code suivant :Tuple equality performs lifted conversions if one of the tuples is a nullable tuple, as shown in the following code:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple); // Also true

L’égalité de tuple effectue également des conversions implicites sur chaque membre de deux tuples.Tuple equality also performs implicit conversions on each member of both tuples. Cela inclut les conversions « lifted », les conversions étendues ou d’autres conversions implicites.These include lifted conversions, widening conversions, or other implicit conversions. Les exemples suivants montrent qu'un tuple entier à 2 éléments peut être comparé à un tuple long à 2 éléments en raison de la conversion implicite d'entier à long :The following examples show that an integer 2-tuple can be compared to a long 2-tuple because of the implicit conversion from integer to long:

// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true

// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true

// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true

Les noms des membres du tuple ne participent pas aux tests d'égalité.The names of the tuple members do not participate in tests for equality. Cependant, si l'un des opérandes est un tuple littéral avec des noms explicites, le compilateur génère une alerte CS8383 si ces noms ne correspondent pas aux noms des autres opérandes.However, if one of the operands is a tuple literal with explicit names, the compiler generates warning CS8383 if those names do not match the names of the other operand. Si les deux opérandes sont des tuples littéraux, l'avertissement est généré pour l'opérande de droite, comme le montre l'exemple ci-dessous :In the case where both operands are tuple literals, the warning is on the right operand as shown in the following example:

(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another); // true. Member names don't participate.
Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names

Enfin, les tuples peuvent contenir des tuples imbriqués.Finally, tuples may contain nested tuples. L’égalité de tuple compare la « forme » de chaque opérande via des tuples imbriqués, comme le montre l’exemple suivant :Tuple equality compares the "shape" of each operand through nested tuples as shown in the following example:

(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );

Comparer deux tuples pour vérifier leur égalité (ou leur inégalité) lorsqu’ils ont des formes différentes est une erreur de compilation.It's a compile time error to compare two tuples for equality (or inequality) when they have different shapes. Le compilateur ne tentera pas de déconstruire des tuples imbriqués afin de les comparer.The compiler won't attempt any deconstruction of nested tuples in order to compare them.

Affectation et tuplesAssignment and tuples

Le langage prend en charge l’affectation entre les types de tuples ayant le même nombre d’éléments, où chaque élément de la partie droite peut être converti implicitement en son élément correspondant de la partie gauche.The language supports assignment between tuple types that have the same number of elements, where each right-hand side element can be implicitly converted to its corresponding left-hand side element. D’autres conversions ne sont pas prises en compte pour les affectations.Other conversions aren't considered for assignments. Affecter un tuple à un autre lorsqu’ils ont des formes différentes est une erreur de compilation.It's a compile time error to assign one tuple to another when they have different shapes. Le compilateur ne tentera pas de déconstruire des tuples imbriqués afin de les affecter.The compiler won't attempt any deconstruction of nested tuples in order to assign them. Examinons les types d’affectation qui sont autorisés entre les types tuple.Let's look at the kinds of assignments that are allowed between tuple types.

Prenez en compte les variables utilisées dans les exemples suivants :Consider these variables used in the following examples:

// The 'arity' and 'shape' of all these tuples are compatible. 
// The only difference is the field names being used.
var unnamed = (42, "The meaning of life");
var anonymous = (16, "a perfect square");
var named = (Answer: 42, Message: "The meaning of life");
var differentNamed = (SecretConstant: 42, Label: "The meaning of life");

Les deux premières variables, unnamed et anonymous, n’ont pas de noms sémantiques fournis pour les éléments.The first two variables, unnamed and anonymous do not have semantic names provided for the elements. Les noms des champs sont Item1 et Item2.The field names are Item1 and Item2. Les deux dernières variables, named et differentName, ont des noms sémantiques fournis pour les éléments.The last two variables, named and differentName have semantic names given for the elements. Ces deux tuples ont des noms différents pour les éléments.These two tuples have different names for the elements.

Ces quatre tuples ont le même nombre d’éléments (appelé « cardinalité ») et les types de ces éléments sont identiques.All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. Par conséquent, toutes ces affectations fonctionnent :Therefore, all of these assignments work:

unnamed = named;

named = unnamed;
// 'named' still has fields that can be referred to
// as 'answer', and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// unnamed to unnamed:
anonymous = unnamed;

// named tuples.
named = differentNamed;
// The field names are not assigned. 'named' still has 
// fields that can be referred to as 'answer' and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// With implicit conversions:
// int can be implicitly converted to long
(long, string) conversion = named;

Notez que les noms des tuples ne sont pas affectés.Notice that the names of the tuples are not assigned. Les valeurs des éléments sont affectées suivant l’ordre des éléments dans le tuple.The values of the elements are assigned following the order of the elements in the tuple.

Des tuples de types différents ou n’ayant pas le même nombre d’éléments ne sont pas attribuables :Tuples of different types or numbers of elements are not assignable:

// Does not compile.
// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
var differentShape = (1, 2, 3);
named = differentShape;

Tuples comme valeurs de retour de méthodeTuples as method return values

L’une des utilisations les plus courantes des tuples est en tant que valeur de retour de méthode.One of the most common uses for tuples is as a method return value. Examinons en détail un exemple.Let's walk through one example. Considérons cette méthode qui calcule l’écart type d’une suite de nombres :Consider this method that computes the standard deviation for a sequence of numbers:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    // Step 1: Compute the Mean:
    var mean = sequence.Average();

    // Step 2: Compute the square of the differences between each number 
    // and the mean:
    var squaredMeanDifferences = from n in sequence
                                 select (n - mean) * (n - mean);
    // Step 3: Find the mean of those squared differences:
    var meanOfSquaredDifferences = squaredMeanDifferences.Average();

    // Step 4: Standard Deviation is the square root of that mean:
    var standardDeviation = Math.Sqrt(meanOfSquaredDifferences);
    return standardDeviation;
}

Notes

Ces exemples calculent l’écart-type empirique non corrigé.These examples compute the uncorrected sample standard deviation. La formule de l’écart-type empirique corrigé diviserait la somme des écarts au carré par rapport à la moyenne par (N-1) au lieu de N, comme avec la méthode d’extension Average.The corrected sample standard deviation formula would divide the sum of the squared differences from the mean by (N-1) instead of N, as the Average extension method does. Pour plus d’informations sur les différences entre ces formules de calcul d’écart-type, consultez un texte de statistiques.Consult a statistics text for more details on the differences between these formulas for standard deviation.

Le code précédent utilise la formule classique de calcul de l’écart-type.The preceding code follows the textbook formula for the standard deviation. Elle génère la réponse correcte, mais constitue une implémentation peu efficace.It produces the correct answer, but it's an inefficient implementation. Cette méthode énumère deux fois la séquence : une fois pour générer la moyenne et une fois pour générer la moyenne quadratique des écarts par rapport à la moyenne.This method enumerates the sequence twice: Once to produce the average, and once to produce the average of the square of the difference of the average. (Souvenez-vous que les requêtes LINQ sont évaluées de manière différée, de sorte que le calcul des écarts par rapport à la moyenne et de la moyenne de ces écarts effectue une seule énumération.)(Remember that LINQ queries are evaluated lazily, so the computation of the differences from the mean and the average of those differences makes only one enumeration.)

Il existe une formule alternative qui calcule l’écart-type à l’aide d’une seule énumération de la suite.There is an alternative formula that computes standard deviation using only one enumeration of the sequence. Ce calcul génère deux valeurs en énumérant la suite : la somme de tous les éléments de la suite et la somme de chaque valeur au carré :This computation produces two values as it enumerates the sequence: the sum of all items in the sequence, and the sum of the each value squared:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    double count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Cette version énumère une seule fois la séquence.This version enumerates the sequence exactly once. Toutefois, ce code n’est pas réutilisable.But it's not reusable code. En poursuivant votre travail, vous trouverez que de nombreux calculs statistiques différents utilisent le nombre d’éléments dans la suite, la somme de la suite et la somme des carrés de la suite.As you keep working, you'll find that many different statistical computations use the number of items in the sequence, the sum of the sequence, and the sum of the squares of the sequence. Refactorisons cette méthode et écrivons une méthode utilitaire qui génère ces trois valeurs.Let's refactor this method and write a utility method that produces all three of those values. Les trois valeurs peuvent toutes être retournées sous forme de tuple.All three values can be returned as a tuple.

Mettons à jour cette méthode afin de stocker dans un tuple les trois valeurs calculées pendant l’énumération.Let's update this method so the three values computed during the enumeration are stored in a tuple. La version suivante est créée :That creates this version:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = (Count: 0, Sum: 0.0, SumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.Count++;
        computation.Sum += item;
        computation.SumOfSquares += item * item;
    }

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

La prise en charge de la refactorisation par Visual Studio facilite l’extraction des fonctionnalités pour les statistiques principales dans une méthode privée.Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. Cela vous donne une méthode private static qui retourne le type tuple avec les trois valeurs de Sum, SumOfSquares et Count :That gives you a private static method that returns the tuple type with the three values of Sum, SumOfSquares, and Count:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int Count, double Sum, double SumOfSquares) computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    var computation = (count: 0, sum: 0.0, sumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.count++;
        computation.sum += item;
        computation.sumOfSquares += item * item;
    }

    return computation;
}

Le langage met à votre disposition plusieurs autres options, si vous souhaitez apporter quelques modifications rapides à la main.The language enables a couple more options that you can use, if you want to make a few quick edits by hand. Tout d’abord, vous pouvez utiliser la déclaration var pour initialiser le résultat de tuple à partir de l’appel de la méthode ComputeSumAndSumOfSquares.First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. Vous pouvez également créer trois variables discrètes à l’intérieur de la méthode ComputeSumAndSumOfSquares.You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. Le code suivant montre la version finale :The final version is shown in the following code:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (count, sum, sumOfSquares);
}

Cette version finale peut être utilisée pour toute méthode qui a besoin de ces trois valeurs ou d’un sous-ensemble quelconque d’entre elles.This final version can be used for any method that needs those three values, or any subset of them.

Le langage prend en charge d’autres options pour gérer les noms des éléments dans ces méthodes retournant des tuples.The language supports other options in managing the names of the elements in these tuple-returning methods.

Vous pouvez supprimer les noms des champs de la déclaration de la valeur de retour et retourner un tuple sans nom :You can remove the field names from the return value declaration and return an unnamed tuple:

private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (sum, sumOfSquares, count);
}

Les champs de ce tuple sont nommés Item1, Item2, et Item3.The fields of this tuple are named Item1, Item2, and Item3. Il est recommandé de fournir des noms sémantiques aux éléments de tuples retournés par les méthodes.It's recommended that you provide semantic names to the elements of tuples returned from methods.

Un autre idiome pour lequel les tuples peuvent être très utiles est lorsque vous créez des requêtes LINQ.Another idiom where tuples can be useful is when you are authoring LINQ queries. Le résultat final est une projection qui contient certaines propriétés des objets sélectionnés, mais pas toutes.The final projected result often contains some, but not all, of the properties of the objects being selected.

En règle générale, vous devez projeter les résultats de la requête dans une suite d’objets constituant un type anonyme.You would traditionally project the results of the query into a sequence of objects that were an anonymous type. Cela présentait de nombreuses limitations, principalement parce que les types anonymes ne pouvaient pas facilement être nommés dans le type de retour pour une méthode.That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. Des coûts importants accompagnaient les alternatives utilisant object ou dynamic comme type du résultat.Alternatives using object or dynamic as the type of the result came with significant performance costs.

Il est aisé de retourner une séquence d’un type tuple, et les noms et types des éléments sont disponibles au moment de la compilation et via les outils de l’IDE.Returning a sequence of a tuple type is easy, and the names and types of the elements are available at compile time and through IDE tools. Considérons, par exemple, une application d’agenda.For example, consider a ToDo application. Vous pouvez définir une classe similaire à la suivante pour représenter une entrée unique dans la liste des tâches :You might define a class similar to the following to represent a single entry in the ToDo list:

public class ToDoItem
{
    public int ID { get; set; }
    public bool IsDone { get; set; }
    public DateTime DueDate { get; set; }
    public string Title { get; set; }
    public string Notes { get; set; }    
}

Vos applications mobiles peuvent prendre en charge une forme compacte des éléments de tâche en cours qui affiche uniquement le titre.Your mobile applications may support a compact form of the current ToDo items that only displays the title. Cette requête LINQ effectuerait une projection incluant uniquement l’ID et le titre.That LINQ query would make a projection that includes only the ID and the title. Une méthode retournant une suite de tuples exprime bien cette conception :A method that returns a sequence of tuples expresses that design well:

internal IEnumerable<(int ID, string Title)> GetCurrentItemsMobileList()
{
    return from item in AllItems
           where !item.IsDone
           orderby item.DueDate
           select (item.ID, item.Title);
}

Notes

Dans C# 7.1, les projections de tuple vous permettent de créer des tuples nommés à l’aide d’éléments, d’une manière semblable au nommage des propriétés dans les types anonymes.In C# 7.1, tuple projections enable you to create named tuples using elements, in a manner similar to the property naming in anonymous types. Dans le code ci-dessus, l’instruction select dans la projection de requête crée un tuple qui a les éléments ID et Title.In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

Le tuple nommé peut faire partie de la signature.The named tuple can be part of the signature. Il permet au compilateur et aux outils de l’IDE de vérifier de façon statique que vous utilisez correctement le résultat.It lets the compiler and IDE tools provide static checking that you are using the result correctly. Le tuple nommé contient également les informations de type statique, si bien qu’il n’est pas nécessaire d’utiliser des fonctionnalités d’exécution coûteuses telles que la réflexion ou la liaison dynamique pour utiliser les résultats.The named tuple also carries the static type information so there is no need to use expensive run time features like reflection or dynamic binding to work with the results.

DéconstructionDeconstruction

Vous pouvez désassembler tous les éléments d’un tuple en déconstruisant le tuple retourné par une méthode.You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. Il existe trois approches différentes de la déconstruction des tuples.There are three different approaches to deconstructing tuples. Tout d’abord, vous pouvez déclarer explicitement le type de chacun des champs à l’intérieur des parenthèses afin de créer des variables discrètes pour tous les éléments du tuple :First, you can explicitly declare the type of each field inside parentheses to create discrete variables for each of the elements in the tuple:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int count, double sum, double sumOfSquares) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Vous pouvez également déclarer des variables implicitement typées pour chaque champ d’un tuple à l’aide du mot clé var en dehors des parenthèses :You can also declare implicitly typed variables for each field in a tuple by using the var keyword outside the parentheses:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var (sum, sumOfSquares, count) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Il est également possible d’utiliser le mot clé var avec une ou toutes les déclarations de variables à l’intérieur des parenthèses.It is also legal to use the var keyword with any, or all of the variable declarations inside the parentheses.

(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence);

Vous ne pouvez pas utiliser un type spécifique en dehors des parenthèses, même si tous les champs du tuple ont le même type.You cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

Vous pouvez également déconstruire des tuple avec des déclarations existantes :You can deconstruct tuples with existing declarations as well:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y) => (X, Y) = (x, y);
}

Avertissement

Vous ne pouvez pas mélanger des déclarations existantes avec des déclarations à l’intérieur des parenthèses.You cannot mix existing declarations with declarations inside the parentheses. Par exemple, ce qui suit n’est pas autorisé : (var x, y) = MyMethod();.For instance, the following is not allowed: (var x, y) = MyMethod();. L’erreur CS8184 est générée, car x est déclaré à l’intérieur des parenthèses et y a été précédemment déclaré ailleurs.This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

Déconstruction de types définis par l’utilisateurDeconstructing user-defined types

N’importe quel type tuple peut être déconstruit comme indiqué ci-dessus.Any tuple type can be deconstructed as shown above. Il est également facile d’activer la déconstruction sur n’importe quel type défini par l’utilisateur (classes, structs ou même interfaces).It's also easy to enable deconstruction on any user-defined type (classes, structs, or even interfaces).

L’auteur du type peut définir une ou plusieurs méthodes Deconstruct qui affectent des valeurs à un nombre quelconque de variables out qui représentent les éléments de données qui composent le type.The type author can define one or more Deconstruct methods that assign values to any number of out variables representing the data elements that make up the type. Par exemple, le type Person suivant définit une méthode Deconstruct qui déconstruit un objet person en éléments représentant le prénom et le nom de famille :For example, the following Person type defines a Deconstruct method that deconstructs a person object into the elements representing the first name and last name:

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

La méthode deconstruct permet l’attribution à partir d’un objet Person de deux chaînes représentant les propriétés FirstName et LastName :The deconstruct method enables assignment from a Person to two strings, representing the FirstName and LastName properties:

var p = new Person("Althea", "Goodwin");
var (first, last) = p;

Vous pouvez activer la déconstruction même pour des types que vous n’avez pas créés.You can enable deconstruction even for types you did not author. La méthode Deconstruct peut être une méthode d’extension qui désassemble les membres de données accessibles d’un objet.The Deconstruct method can be an extension method that unpackages the accessible data members of an object. L’exemple ci-dessous montre un type Student, dérivé du type Person, et une méthode d’extension qui déconstruit un objet Student en trois variables, qui représentent les propriétés FirstName, LastName et GPA :The example below shows a Student type, derived from the Person type, and an extension method that deconstructs a Student into three variables, representing the FirstName, the LastName, and the GPA:

public class Student : Person
{
    public double GPA { get; }
    public Student(string first, string last, double gpa) :
        base(first, last)
    {
        GPA = gpa;
    }
}

public static class Extensions
{
    public static void Deconstruct(this Student s, out string first, out string last, out double gpa)
    {
        first = s.FirstName;
        last = s.LastName;
        gpa = s.GPA;
    }
}

Un objet Student a désormais deux méthodes Deconstruct accessibles : la méthode d’extension déclarée pour les types Student et le membre du type Person.A Student object now has two accessible Deconstruct methods: the extension method declared for Student types, and the member of the Person type. Les deux sont dans la portée et cela permet à un objet Student d’être déconstruit en deux ou trois variables.Both are in scope, and that enables a Student to be deconstructed into either two variables or three. Si vous affectez un étudiant à trois variables, le prénom, le nom de famille et la moyenne pondérée cumulative (GPA) sont tous retournés.If you assign a student to three variables, the first name, last name, and GPA are all returned. Si vous affectez un étudiant à deux variables, seuls le prénom et le nom de famille sont retournés.If you assign a student to two variables, only the first name and the last name are returned.

var s1 = new Student("Cary", "Totten", 4.5);
var (fName, lName, gpa) = s1;

Vous devez être prudent en définissant plusieurs méthodes Deconstruct dans une classe ou une hiérarchie de classes.You should be careful defining multiple Deconstruct methods in a class or a class hierarchy. Plusieurs méthodes Deconstruct ayant le même nombre de paramètres out peuvent rapidement entraîner des ambiguïtés.Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. Les appelants peuvent ne pas être en mesure d’appeler facilement la méthode Deconstruct souhaitée.Callers may not be able to easily call the desired Deconstruct method.

Cet exemple présente un risque minimal d’appel ambigu, car la méthode Deconstruct pour Person a deux paramètres de sortie, et la méthode Deconstruct pour Student en a trois.In this example, there is minimal chance for an ambiguous call because the Deconstruct method for Person has two output parameters, and the Deconstruct method for Student has three.

Les opérateurs de déconstruction ne participent pas aux tests d’égalité.Deconstruction operators do not participate in testing equality. L’exemple suivant génère une erreur de compilation CS0019 :The following example generates compiler error CS0019:

Person p = new Person("Althea", "Goodwin");
if (("Althea", "Goodwin") == p)
    Console.WriteLine(p);

La méthode Deconstruct pourrait convertir l’objet Person p en un tuple contenant les deux chaînes, mais elle ne s’applique pas dans le contexte des tests d’égalité.The Deconstruct method could convert the Person object p to a tuple containing two strings, but it is not applicable in the context of equality tests.

ConclusionConclusion

La nouvelle prise en charge du langage et de la bibliothèque pour les tuples nommés facilite grandement l’utilisation de conceptions utilisant des structures de données qui stockent plusieurs éléments mais ne définissent pas de comportement, telles que les classes et les structs.The new language and library support for named tuples makes it much easier to work with designs that use data structures that store multiple elements but do not define behavior, as classes and structs do. Il est facile et rapide d’utiliser des tuples pour ces types.It's easy and concise to use tuples for those types. Vous obtenez tous les avantages d’une vérification de type statique, sans avoir à créer de types au moyen de la syntaxe class ou struct plus détaillée.You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. Même ainsi, ils sont particulièrement utiles pour les méthodes utilitaires private ou internal.Even so, they are most useful for utility methods that are private, or internal. Créez des types définis par l’utilisateur, des types class ou struct, quand vos méthodes publiques retournent une valeur comportant plusieurs éléments.Create user-defined types, either class or struct types when your public methods return a value that has multiple elements.