Cet article fait partie d’une série d’articles sur les apports fonctionnels de C# 7 (i.e. C# 7.0/7.1/7.2/7.3).
Amélioration à partir de C# 7.0
Initialisation
Utiliser des noms de membres explicites
Noms de membres déterminés par des variables existantes
Utiliser .Item1, .Item2, …, .Item<N> est toujours possible
Affectation entre System.ValueTuple
Déconstruction
Ignorer une variable inutile
Comparaison entre tuples
Comparaison entre System.Tuple
Comparaison entre System.ValueTuple
Les tuples sont des structures de données permettant de stocker un nombre variable d’objets de type différent. L’intêret est d’éviter à avoir à déclarer la structure explicitement. Les objets sont stockés dans les membres du tuple. Les membres contenant les objets sont .Item1
, .Item2
, …, .Item<N>
.
Historiquement, les tuples sont de type System.Tuple
(apparu avec le framework .NET 4.0). System.Tuple
est un type permettant de créer des objets de type référence.
Le type et le nombre de membres contenus dans le tuple sont indiqués à l’initialisation:
Tuple<int, string, float> tuple = new Tuple<int, string, float>(5, "5", 5.0f);
Dans cet exemple, le tuple contient 3 membres:
Item1
de typeint
initialisé à la valeur5
Item2
de typestring
initialisé à la valeur"5"
Item3
de typefloat
initialisé à la valeur5.0f
On peut aussi instancier un tuple de type System.Tuple
en utilisant la syntaxe:
Tuple<int, string, float> tuple = Tuple.Create(5, "5", 5.0f);
Avant C# 7.0, les tuples devaient être utilisés exclusivement avec des noms de membres génériques (i.e. .Item1
, .Item2
, …, .Item<N>
) ce qui rendait le code peu clair:
Tuple<int, string, float> tuple = new Tuple<int, string, float>(5, "5", 5.0f);
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
Console.WriteLine(tuple.Item3);
System.ValueTuple
A partir du framework .NET 4.7 est apparu le type System.ValueTuple
permettant de créer des objets équivalent à System.Value
. La principale différence entre ces 2 types est:
System.Tuple
est une classe, elle permet de créer des objets de type référence.System.ValueTuple
est une structure et permet donc, de créer des objets de type valeur.
System.ValueTuple
est fonctionnellement très proche de System.Tuple
. Par exemple, on peut initialiser des objets System.ValueTuple
avec une syntaxe semblable en utilisant la méthode statique ValueTuple.Create()
:
var tuple = ValueTuple.Create(5, "5", 5.0f);
Au niveau de la syntaxe, C# 7.0 apporte des améliorations pour faciliter l’initialisation des objets de type System.ValueTuple
.
Pour utiliser le type System.ValueTuple
en utilisant le framework .NET 4.6.2 ou antérieur, il faut installer le package NuGet System.ValueTuple:
install-package System.ValueTuple
Amélioration à partir de C# 7.0
C# 7.0 permet de rendre la syntaxe plus compacte pour initialiser des objets de type System.ValueTuple
et rends plus clair l’accès à ses membres.
Initialisation
A partir de C# 7.0, on peut initialiser les objets System.ValueTuple
de cette façon:
(int, string, float) tuple = (5, "5", 5.0f);
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
Console.WriteLine(tuple.Item3);
Utiliser des noms de membres explicites
Pour rendre l’accès aux membres plus explicite, on peut désormais nommer les membres:
(int ValueAsInt, string ValueAsString, float ValueAsFloat) tuple = (5, "5", 5.0f);
Console.WriteLine(tuple.ValueAsInt);
Console.WriteLine(tuple.ValueAsString);
Console.WriteLine(tuple.ValueAsFloat);
Une autre syntaxe est possible à l’initialisation pour indiquer les noms de membres:
var tuple = (ValueAsInt: 5, ValueAsString: "5", ValueAsFloat: 5.0f);
Console.WriteLine(tuple.ValueAsInt);
Console.WriteLine(tuple.ValueAsString);
Console.WriteLine(tuple.ValueAsFloat);
Noms de membres déterminés par des variables existantes
A partir de C# 7.1, lors de l’initialisation d’un tuple, il n’est pas obligatoire de préciser le nom et le type des éléments du tuple si on l’initialise à partir de variables déjà existantes. Le nom et le type sont déterminés à partir des variables existantes:
int valueAsInt = 5;
string valueAsString = "5";
float valueAsFloat = 5.0f;
var tuple = (valueAsInt, valueAsString, valueAsFloat); // Le nom et le type des éléments du tuple
// sont déterminés en fonction des noms et types des variables.
Console.WriteLine(tuple.valueAsInt);
Console.WriteLine(tuple.valueAsString);
Console.WriteLine(tuple.valueAsFloat);
Utiliser .Item1, .Item2, …, .Item<N> est toujours possible
Même si on utilise des noms de membres dont le nom est explicite, les anciens membres .Item1
, .Item2
, …, .Item<N>
restent toujours utilisables:
(int ValueAsInt, string ValueAsString, float ValueAsFloat) tuple = (5, "5", 5.0f);
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
Console.WriteLine(tuple.Item3);
System.ValueTuple
est mutable
Bien-que System.ValueTuple
est une structure permettant de créer des objets de type valeur, il est mutable à l’inverse de System.Tuple
qui est immutable. Il est, ainsi, possible de modifier la valeur des membres .Item1
, .Item2
, …, .Item<N>
après instanciation d’un objet de type System.ValueTuple
:
(int, string, float) valObjectTuple = (5, "5", 5.0f);
valObjectTuple.Item1 = 7; // OK
A l’inverse, modifier un objet System.Tuple
n’est pas possible:
Tuple<int, string, float> refObjectTuple = new Tuple<int, string, float>(5, "5", 5.0f);
refObjectTuple.Item1 = 7; // ERROR
Affectation entre System.ValueTuple
On peut effectuer des affectations entre objets de type System.ValueTuple
en modifiant les noms des membres:
var tuple = (ValueAsInt: 5, ValueAsString: "5", ValueAsFloat: 5.0f);
(int NewValueAsInt, string NewValueAsString, float NewValueAsFloat) newTuple = tuple;
Console.WriteLine(newTuple.NewValueAsInt);
Console.WriteLine(newTuple.NewValueAsString);
Console.WriteLine(newTuple.NewValueAsFloat);
Cette syntaxe n’est pas possible avec des objets de type System.Tuple
.
Déconstruction
La déconstruction permet d’affecter les membres d’un tuple dans des variables distinctes (ces syntaxes sont possibles pour les types System.Tuple
et System.ValueTuple
):
var tuple = ValueTuple.Create(5, "5", 5.0f);
(int valueAsInt, string valueAsString, float valueAsFloat) = tuple;
Console.WriteLine(valueAsInt);
Console.WriteLine(valueAsString);
Console.WriteLine(valueAsFloat);
Une autre syntaxe est équivalente en utilisant le mot clé var
:
var (valueAsInt, valueAsString, valueAsFloat) = tuple;
Console.WriteLine(valueAsInt);
Console.WriteLine(valueAsString);
Console.WriteLine(valueAsFloat);
Si on utilise des variables existantes:
int valueAsInt;
string valueAsString;
float valueAsFloat;
(valueAsInt, valueAsString, valueAsFloat) = tuple;
Console.WriteLine(valueAsInt);
Console.WriteLine(valueAsString);
Console.WriteLine(valueAsFloat);
Ignorer une variable inutile
Lors d’une déconstruction d’un tuple, il est possible d’ignorer une variable inutile en utilisant le caractère _
de façon à alléger la syntaxe:
var tuple = (6, "6", 6.0f);
var (ValueAsInt, ValueAsString, ValueAsFloat) = tuple; // Déconstruction du tuple
Console.WriteLine($"Int value is {ValueAsInt}."); // ValueAsString et ValueAsFloat sont inutiles
On peut ignorer de déclarer ces variables lors de la déconstruction:
var (ValueAsInt, _, _) = tuple; // Déconstruction
Console.WriteLine($"Int value is {ValueAsInt}.");
Cette syntaxe est possible pour les types System.Tuple
et System.ValueTuple
.
Comparaison entre tuples
La comparaison entre tuples en utilisant les opérateurs ==
ou !=
n’est pas la même suivant si on utilise des objets de type System.Tuple
ou System.ValueTuple
.
Comparaison entre System.Tuple
L’utilisation des opérateurs ==
et =!
avec des tuples de type System.Tuple
respecte les mêmes règles que pour tous les objets de type référence en .NET: par défaut la comparaison s’effectue sur la référence des objets:
var refTuple1 = Tuple.Create(5, "5", 5.0f);
var refTuple2 = Tuple.Create(5, "5", 5.0f);
Console.WriteLine(refTuple1 == refTuple2); // False
Pour effectuer une comparaison entre les membres des objets, il faut utiliser la surcharge Equals()
:
Console.WriteLine(refTuple1.Equals(refTuple2); // True
Comparaison entre System.ValueTuple
Dans le cas de System.ValueTuple
(avant C# 7.3), l’utilisation des opérateurs ==
et =!
n’est pas possible, il faut utiliser CompareTo()
ou Equals()
:
var valTuple1 = (5, "5", 5.0f);
var valTuple2 = (5, "5", 5.0f);
Console.WriteLine(valTuple1.CompareTo(valTuple2)); // 0 en cas d'égalité
Console.WriteLine(valTuple1.Equals(valTuple2)); // True
A partir de C# 7.3, il est possible d’utiliser les opérateurs ==
et =!
pour effectuer une comparaison des membres des tuples:
Console.WriteLine(valTuple1 == valTuple2); // True
- ValueTuple Struct: https://docs.microsoft.com/en-us/dotnet/api/system.valuetuple?view=netcore-3.1
- Tuple types (C# reference): https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples
- How to make a list of tuples with named values: https://peterdaugaardrasmussen.com/2020/02/22/c-how-to-make-a-list-of-value-tuples-with-named-values/
- How to compare two ValueTuple in C#?:
https://www.geeksforgeeks.org/how-to-compare-two-valuetuple-in-c-sharp/ - How to easily initialize a list of Tuples?: https://stackoverflow.com/questions/8002455/how-to-easily-initialize-a-list-of-tuples
- is the ValueTuple structure only mutable as variable?: https://stackoverflow.com/questions/43852501/is-the-valuetuple-structure-only-mutable-as-variable
- Tuple deconstruction in C# 7: https://thomaslevesque.com/2016/08/23/tuple-deconstruction-in-c-7/