Quelle est la différence entre System.ValueTuple et System.Tuple?

J’ai décompilé des bibliothèques C # 7 et vu les génériques ValueTuple utilisés. Que sont les ValueTuples et pourquoi pas Tuple place?

  • https://docs.microsoft.com/en-gb/dotnet/api/system.tuple
  • https://docs.microsoft.com/en-gb/dotnet/api/system.valuetuple

Qu’est-ce que ValuedTuples et pourquoi pas Tuple place?

Un ValueTuple est une structure qui reflète un tuple, identique à la classe System.Tuple origine.

La principale différence entre ValueTuple et ValueTuple est la suivante:

  • System.ValueTuple est un type de valeur (struct), tandis que System.Tuple est un type de référence ( class ). Ceci est significatif quand on parle d’allocations et de pression du GC.
  • System.ValueTuple n’est pas seulement une struct , c’est une struct mutable , et il faut faire attention lorsque vous les utilisez comme tel. Pensez à ce qui se passe quand une classe détient un System.ValueTuple tant que champ.
  • System.ValueTuple expose ses éléments via des champs au lieu de propriétés.

Jusqu’à C # 7, l’utilisation de tuples n’était pas très pratique. Leurs noms de champs sont Item2 , Item2 , etc., et le langage n’a pas fourni de sucre de syntaxe comme la plupart des autres langages (Python, Scala).

Lorsque l’équipe de conception de langage .NET a décidé d’incorporer des tuples et de leur append du sucre syntaxique au niveau de la langue, un facteur important était la performance. Avec ValueTuple étant un type de valeur, vous pouvez éviter la pression du GC lorsque vous les utilisez car (en tant que détail d’implémentation), ils seront alloués sur la stack.

De plus, une struct obtient automatiquement une sémantique d’égalité (peu profonde) par le moteur d’exécution, contrairement à une class . Bien que l’équipe de conception ait veillé à une égalité encore plus optimisée pour les tuples, elle a donc implémenté une égalité personnalisée.

Voici un paragraphe des notes de conception de Tuples :

Struct ou Class:

Comme mentionné, je propose de faire des structs type tuple plutôt que des classes , de sorte qu’aucune pénalité d’allocation ne leur soit associée. Ils doivent être aussi légers que possible.

On peut dire que les structs peuvent finir par être plus coûteuses, car l’atsortingbution copie une valeur plus grande. Donc, si on leur atsortingbue beaucoup plus qu’ils ne sont créés, les structs seraient un mauvais choix.

Dans leur motivation même, les tuples sont éphémères. Vous les utiliseriez lorsque les pièces sont plus importantes que l’ensemble. Le modèle commun serait donc de les construire, de les retourner et de les déconstruire immédiatement. Dans cette situation, les structures sont clairement préférables.

Les structures ont également un certain nombre d’autres avantages, qui deviendront évidents dans ce qui suit.

Exemples:

Vous pouvez facilement voir que travailler avec System.Tuple devient très rapidement ambigu. Par exemple, disons que nous avons une méthode qui calcule une sum et un décompte d’une List :

 public Tuple DoStuff(IEnumerable ints) { var sum = 0; var count = 0; foreach (var value in values) { sum += value; count++; } return new Tuple(sum, count); } 

À la réception, on se retrouve avec:

 Tuple result = DoStuff(Enumerable.Range(0, 10)); // What is Item1 and what is Item2? // Which one is the sum and which is the count? Console.WriteLine(result.Item1); Console.WriteLine(result.Item2); 

La façon dont vous pouvez déconstruire les tuples de valeurs en arguments nommés est la véritable puissance de la fonctionnalité:

 public (int sum, int count) DoStuff(IEnumerable values) { var res = (sum: 0, count: 0); foreach (var value in values) { res.sum += value; res.count++; } return res; } 

Et à la réception:

 var result = DoStuff(Enumerable.Range(0, 10)); Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}"); 

Ou:

 var (sum, count) = DoStuff(Enumerable.Range(0, 10)); Console.WriteLine($"Sum: {sum}, Count: {count}"); 

Goodies du compilateur:

Si nous regardons sous la couverture de notre exemple précédent, nous pouvons voir exactement comment le compilateur interprète ValueTuple lorsque nous lui demandons de déconstruire:

 [return: TupleElementNames(new ssortingng[] { "sum", "count" })] public ValueTuple DoStuff(IEnumerable values) { ValueTuple result; result..ctor(0, 0); foreach (int current in values) { result.Item1 += current; result.Item2++; } return result; } public void Foo() { ValueTuple expr_0E = this.DoStuff(Enumerable.Range(0, 10)); int item = expr_0E.Item1; int arg_1A_0 = expr_0E.Item2; } 

En interne, le code compilé utilise Item2 et Item2 , mais tout cela est séparé de nous puisque nous travaillons avec un tuple décomposé. Un tuple avec des arguments nommés est annoté avec le TupleElementNamesAtsortingbute . Si nous utilisons une seule variable au lieu de la décomposer, nous obtenons:

 public void Foo() { ValueTuple valueTuple = this.DoStuff(Enumerable.Range(0, 10)); Console.WriteLine(ssortingng.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2)); } 

Notez que le compilateur doit encore faire de la magie (via l’atsortingbut) lorsque nous déboguons notre application, car il serait étrange de voir Item2 , Item2 .

La différence entre ValueTuple et ValueTuple est que ValueTuple est un type de référence et ValueTuple est un type de valeur. Ce dernier point est souhaitable car les modifications apscopes au langage dans C # 7 ont des tuples utilisés beaucoup plus fréquemment, mais l’atsortingbution d’un nouvel object sur le tas pour chaque tuple est un problème de performance, en particulier lorsqu’il est inutile.

Cependant, dans C # 7, l’idée est que vous n’avez jamais à utiliser explicitement les deux types à cause du sucre de syntaxe ajouté pour l’utilisation du tuple. Par exemple, dans C # 6, si vous souhaitez utiliser un tuple pour renvoyer une valeur, vous devez procéder comme suit:

 public Tuple GetValues() { // ... return new Tuple(ssortingngVal, intVal); } var value = GetValues(); ssortingng s = value.Item1; 

Cependant, en C # 7, vous pouvez utiliser ceci:

 public (ssortingng, int) GetValues() { // ... return (ssortingngVal, intVal); } var value = GetValues(); ssortingng s = value.Item1; 

Vous pouvez même aller plus loin et donner les noms de valeurs:

 public (ssortingng S, int I) GetValues() { // ... return (ssortingngVal, intVal); } var value = GetValues(); ssortingng s = value.S; 

… ou déconstruire entièrement le tuple:

 public (ssortingng S, int I) GetValues() { // ... return (ssortingngVal, intVal); } var (S, I) = GetValues(); ssortingng s = S; 

Les tuples n’étaient pas souvent utilisés dans C # pre-7 car ils étaient encombrants et verbeux, et n’étaient vraiment utilisés que dans les cas où la construction d’une classe de données / structure pour une seule instance de travail poserait plus de problèmes que cela en valait la peine. Mais en C # 7, les tuples ont une prise en charge au niveau de la langue, donc leur utilisation est beaucoup plus propre et utile.

J’ai regardé la source à la fois pour ValueTuple et ValueTuple . La différence est que Tuple est une class et ValueTuple est une struct qui implémente IEquatable .

Cela signifie que ValueTuple == ValueTuple renverra false si elles ne sont pas la même instance, mais ValueTuple == ValueTuple renverra true si elles sont du même type et Equals renvoie true pour chacune des valeurs qu’elles contiennent.

D’autres réponses ont oublié de mentionner des points importants. Au lieu de reformuler, je vais faire référence à la documentation XML du code source :

Les types ValueTuple (de l’arité 0 à 8) comprennent l’implémentation d’exécution sous-jacente aux tuples en C # et aux struct tuples en F #.

Mis à part la création via la syntaxe du langage , ils sont plus facilement créés via les méthodes d’usine ValueTuple.Create . Les types System.ValueTuple diffèrent des types System.Tuple en ce que:

  • ce sont des structures plutôt que des classes,
  • ils sont mutables plutôt que readonly , et
  • leurs membres (comme Item1, Item2, etc.) sont des champs plutôt que des propriétés.

Avec l’introduction de ce type et du compilateur C # 7.0, vous pouvez facilement écrire

 (int, ssortingng) idAndName = (1, "John"); 

Et renvoyer deux valeurs d’une méthode:

 private (int, ssortingng) GetIdAndName() { //..... return (id, name); } 

Contrairement à System.Tuple vous pouvez mettre à jour ses membres (Mutable) car ce sont des champs publics en lecture-écriture qui peuvent recevoir des noms significatifs:

 (int id, ssortingng name) idAndName = (1, "John"); idAndName.name = "New Name"; 

En plus des commentaires ci-dessus, un des inconvénients de ValueTuple est que, en tant que type de valeur, les arguments nommés sont effacés lors de la compilation en IL, ils ne sont donc pas disponibles pour la sérialisation lors de l’exécution.

c.-à-d. que vos arguments nommés seront toujours “Item1”, “Item2”, etc. lorsqu’ils seront sérialisés via Json.NET par exemple.

Ceci est un très bon résumé de ce que c’est et de sa limitation

https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations