web-dev-qa-db-fra.com

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 ValueTuples et pourquoi pas Tuple à la place?

94
Steve Fan

Que sont ValuedTuples et pourquoi pas Tuple à la place?

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

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

  • System.ValueTuple est un type de valeur (struct), alors 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 un mutable, et il faut faire attention en les utilisant comme tels. Pensez à ce qui se passe lorsqu'une classe contient un System.ValueTuple en tant que champ.
  • System.ValueTuple expose ses éléments via des champs plutôt que des propriétés.

Jusqu'au C 7, utiliser des n-uplets n'était pas très pratique. Leurs noms de champs sont Item1, Item2, etc., et la langue ne leur avait pas fourni de sucre de syntaxe, contrairement à la plupart des autres langues (Python, Scala).

Lorsque l'équipe de conception du langage .NET a décidé d'incorporer des n-uplets et de leur ajouter du sucre de syntaxe au niveau de la langue, un facteur important était la performance. 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), elles seront allouées sur la pile.

De plus, une struct obtient une sémantique d'égalité automatique (peu profonde) par le temps d'exécution, contrairement à class. Bien que l'équipe de conception se soit assurée que l'égalité pour les n-uplets soit encore plus optimisée, une égalité personnalisée a donc été mise en œuvre.

Voici un paragraphe tiré des notes de conception de Tuples :

Struct ou classe:

Comme mentionné, je propose de rendre les types de tuples structs plutôt que classes, de sorte qu'aucune pénalité d'allocation ne leur soit associée. Ils devrait être aussi léger que possible.

On peut soutenir que structs peut finir par être plus coûteux, car l’affectation copie une plus grande valeur. Donc, si on leur attribue beaucoup plus qu'eux sont créés, alors structs serait un mauvais choix.

Dans leur motivation même, les n-uplets sont éphémères. Vous utiliseriez les quand les parties sont plus importantes que le tout. Donc le commun le modèle serait de construire, de retourner et de déconstruire immédiatement leur. Dans cette situation, les structures sont clairement préférables.

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

Exemples:

Vous pouvez facilement voir que travailler avec System.Tuple devient très ambigu. Par exemple, supposons que nous ayons une méthode qui calcule une somme et un nombre de List<Int>:

public Tuple<int, int> DoStuff(IEnumerable<int> ints)
{
    var sum = 0;
    var count = 0;

    foreach (var value in values) { sum += value; count++; }

    return new Tuple(sum, count);
}

A la réception, on se retrouve avec:

Tuple<int, int> 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écomposer les nuplets de valeur en arguments nommés est le vrai pouvoir de la fonctionnalité:

public (int sum, int count) DoStuff(IEnumerable<int> 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 string[] {
    "sum",
    "count"
})]
public ValueTuple<int, int> DoStuff(IEnumerable<int> values)
{
    ValueTuple<int, int> result;
    result..ctor(0, 0);
    foreach (int current in values)
    {
        result.Item1 += current;
        result.Item2++;
    }
    return result;
}

public void Foo()
{
    ValueTuple<int, int> 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 Item1 et Item2, mais tout cela est extrait de nous depuis que nous travaillons avec un tuple décomposé. Un tuple avec des arguments nommés est annoté avec le TupleElementNamesAttribute . Si nous utilisons une seule variable fraîche au lieu de la décomposer, nous obtenons:

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

Notez que le compilateur doit encore faire de la magie (via l'attribut) lors du débogage de notre application, car il serait étrange de voir Item1, Item2.

153
Yuval Itzchakov

La différence entre Tuple et ValueTuple est que Tuple est un type de référence et ValueTuple est un type de valeur. Ce dernier est souhaitable car les modifications apportées au langage dans C # 7 font que les nuplets sont utilisés beaucoup plus fréquemment, mais allouer un nouvel objet sur le tas pour chaque nuplet est un problème de performances, en particulier lorsqu'il est inutile.

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

public Tuple<string, int> GetValues()
{
    // ...
    return new Tuple(stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 

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

public (string, int) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 

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

public (string S, int I) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.S; 

... ou déconstruire entièrement le tuple:

public (string S, int I) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var (S, I) = GetValues();
string s = S;

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

17
Abion47

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

Cela signifie que Tuple == Tuple retournera false s'il ne s'agit pas de la même instance, mais ValueTuple == ValueTuple retournera true si elles sont du même type et Equals renvoie true pour chacune des valeurs qu'elles contiennent.

8
Peter Morris

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

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

Mis à part la création via la syntaxe de langue, elles sont plus facilement créées via les méthodes d'usine ValueTuple.Create . Les types System.ValueTuple diffèrent des types System.Tuple par le fait que:

  • ce sont des structures plutôt que des classes,
  • ils sont mutables plutôt qu'en lecture seule, et
  • leurs membres (tels que 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, string) idAndName = (1, "John");

Et retourne deux valeurs d'une méthode:

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

Contrairement à System.Tuple, vous pouvez mettre à jour ses membres (Mutable) car il s’agit de zones de lecture/écriture publiques. Vous pouvez attribuer des noms significatifs aux champs:

(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
4
Zein Makki

En plus des commentaires ci-dessus, ValueTuple regrette que, comme type de valeur, les arguments nommés sont effacés lors de la compilation en IL, de sorte qu'ils ne sont pas disponibles pour la sérialisation à l'exécution.

c'est-à-dire que vos arguments nommés seront toujours "Item1", "Item2", etc. lorsqu'ils seront sérialisés via e.g. Json.NET.

3
ZenSquirrel

Adhésion tardive pour ajouter une clarification rapide sur ces deux faits:

  • ce sont des structures plutôt que des classes
  • ils sont mutables plutôt qu'en lecture seule

On pourrait penser que changer en masse les nuées de valeur serait simple:

 foreach (var x in listOfValueTuples) { x.Foo = 103; } // wont even compile because x is a value (struct) not a variable

 var d = listOfValueTuples[0].Foo;

Quelqu'un pourrait essayer de contourner ce problème comme ceci:

 // initially *.Foo = 10 for all items
 listOfValueTuples.Select(x => x.Foo = 103);

 var d = listOfValueTuples[0].Foo; // 'd' should be 103 right? wrong! it is '10'

La raison de ce comportement étrange est que les tuples de valeur sont exactement basés sur la valeur (structs) et donc, l'appel .Select (...) fonctionne sur des structures clonées plutôt que sur les originaux. Pour résoudre ce problème, nous devons recourir à:

 // initially *.Foo = 10 for all items
 listOfValueTuples = listOfValueTuples
     .Select(x => {
         x.Foo = 103;
         return x;
     })
     .ToList();

 var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed

Alternativement bien sûr, on pourrait essayer l'approche simple:

   for (var i = 0; i < listOfValueTuples.Length; i++) {
        listOfValueTuples[i].Foo = 103; //this works just fine

        // another alternative approach:
        //
        // var x = listOfValueTuples[i];
        // x.Foo = 103;
        // listOfValueTuples[i] = x; //<-- vital for this alternative approach to work   if you omit this changes wont be saved to the original list
   }

   var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed

J'espère que cela aidera quelqu'un qui a du mal à se faire une belle tête de liste à partir de tuples de valeur hébergés sur des listes.

2
XDS

C’est un très bon résumé de ce qu’il est et de ses limites.

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

0
nick-s