wake-up-neo.com

Was ist der Unterschied zwischen System.ValueTuple und System.Tuple?

Ich habe einige C # 7-Bibliotheken dekompiliert und sah ValueTuple-Generics. Was sind ValueTuples und warum nicht stattdessen Tuple?

94
Steve Fan

Was sind ValuedTuples und warum nicht stattdessen Tuple?

Eine ValueTuple ist eine Struktur, die ein Tuple wiedergibt, genau wie die ursprüngliche System.Tuple-Klasse. 

Der Hauptunterschied zwischen Tuple und ValueTuple ist:

  • System.ValueTuple ist ein Werttyp (struct), während System.Tuple ein Referenztyp (class) ist. Dies ist sinnvoll, wenn es um Zuordnungen und GC-Druck geht.
  • System.ValueTuple ist nicht nur eine struct, sondern eine mutable - und man muss vorsichtig sein, wenn man sie als solche verwendet. Überlegen Sie, was passiert, wenn eine Klasse einen System.ValueTuple als Feld enthält.
  • System.ValueTuple macht seine Elemente über Felder anstelle von Eigenschaften verfügbar.

Bis C # 7 war die Verwendung von Tupeln nicht sehr praktisch. Ihre Feldnamen sind Item1, Item2 usw., und die Sprache hatte keinen Syntaxzucker für sie bereitgestellt, wie dies bei den meisten anderen Sprachen der Fall ist (Python, Scala).

Als das .NET-Sprachentwurfsteam beschloss, Tupel zu integrieren und auf Sprachebene Syntaxzucker hinzuzufügen, war ein wichtiger Faktor die Leistung. Mit ValueTuple als Werttyp können Sie GC-Druck bei der Verwendung vermeiden, da sie (als Implementierungsdetail) auf dem Stack zugewiesen werden.

Außerdem erhält eine Variable struct zur Laufzeit eine automatische (flache) Gleichheitssemantik, während eine Variable class dies nicht tut. Obwohl das Designteam dafür gesorgt hat, dass die Tupel noch besser optimiert werden, wurde eine benutzerdefinierte Gleichheit implementiert.

Hier ist ein Absatz aus den design notes von Tuples :

Struktur oder Klasse:

Wie bereits erwähnt, schlage ich vor, Tuple-Typen structs statt .__ zu erstellen. classes, so dass ihnen keine Zuweisungsstrafe zugeordnet ist. Sie sollte so leicht wie möglich sein.

Wahrscheinlich kann structs teurer werden, weil die Zuweisung kopiert einen größeren Wert. Wenn ihnen also viel mehr zugewiesen wird als __. erstellt werden, wäre structs eine schlechte Wahl.

In ihrer Motivation sind Tupel jedoch vergänglich. Sie würden .__ verwenden. Sie, wenn die Teile wichtiger sind als das Ganze. Also das Gemeinsame Muster wäre zu konstruieren, zurückzukehren und sofort zu dekonstruieren Sie. In dieser Situation sind Strukturen eindeutig vorzuziehen.

Strukturen haben auch eine Reihe weiterer Vorteile, die zu .__ werden. offensichtlich im folgenden.

Beispiele:

Sie können leicht erkennen, dass das Arbeiten mit System.Tuple sehr schnell mehrdeutig wird. Angenommen, wir haben eine Methode, die eine Summe und eine Zählung eines List<Int> berechnet:

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);
}

Auf der Empfangsseite enden wir mit:

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);

Die Art und Weise, wie Sie Wertetupel in benannte Argumente dekonstruieren können, ist die wahre Stärke des Features:

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;
}

Und auf der Empfangsseite:

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

Oder:

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

Compiler-Leckereien:

Wenn wir unter dem Titel unseres vorherigen Beispiels nachsehen, können wir genau sehen, wie der Compiler ValueTuple interpretiert, wenn wir ihn auffordern zu dekonstruieren:

[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;
}

Intern verwendet der kompilierte Code Item1 und Item2, aber dies wird alles abstrahiert, da wir mit einem zerlegten Tuple arbeiten. Ein Tuple mit benannten Argumenten wird mit der TupleElementNamesAttribute kommentiert. Wenn wir eine einzige frische Variable verwenden, anstatt zu zerlegen, erhalten wir:

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));
}

Beachten Sie, dass der Compiler beim Debuggen unserer Anwendung immer noch etwas Magie (über das Attribut) bewirken muss, da es ungeheuer wäre, Item1, Item2 zu sehen.

153
Yuval Itzchakov

Der Unterschied zwischen Tuple und ValueTuple besteht darin, dass Tuple ein Referenztyp und ValueTuple ein Werttyp ist. Letzteres ist wünschenswert, da bei Änderungen an der Sprache in C # 7 Tupel viel häufiger verwendet werden. Die Zuweisung eines neuen Objekts auf dem Heap für jeden Tuple ist jedoch ein Leistungsproblem, insbesondere wenn dies unnötig ist.

In C # 7 besteht jedoch die Idee, dass Sie nie haben explizit einen der beiden Typen verwenden, da der Syntaxzucker für die Tuple-Verwendung hinzugefügt wird. Wenn Sie beispielsweise in C # 6 einen Tuple verwenden möchten, um einen Wert zurückzugeben, müssen Sie Folgendes tun:

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

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

In C # 7 können Sie jedoch Folgendes verwenden:

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

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

Sie können sogar noch einen Schritt weitergehen und den Werten Namen geben:

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

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

... oder den Tupel komplett dekonstruieren:

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

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

Tupel wurden in C # pre-7 nicht häufig verwendet, da sie umständlich und ausführlich waren und nur in Fällen verwendet wurden, in denen das Erstellen einer Datenklasse/-struktur für nur eine einzelne Arbeit mehr Probleme bereitete, als es wert war. In C # 7 haben Tupel jedoch jetzt Unterstützung auf Sprachebene. Daher ist die Verwendung dieser Tools viel sauberer und nützlicher.

17
Abion47

Ich habe nach der Quelle für Tuple und ValueTuple gesucht. Der Unterschied ist, dass Tuple eine class ist und ValueTuple eine struct ist, die IEquatable implementiert.

Das bedeutet, dass Tuple == Tuplefalse zurückgibt, wenn sie nicht dieselbe Instanz sind, aber ValueTuple == ValueTupletrue zurückgibt, wenn sie vom gleichen Typ sind, und Equals für jeden der enthaltenen Werte true zurückgibt.

8
Peter Morris

Bei anderen Antworten wurde vergessen, wichtige Punkte zu erwähnen. Statt der Umformulierung werde ich auf die XML-Dokumentation von source code verweisen:

Die ValueTuple-Typen (von 0 bis 8) umfassen die Laufzeitimplementierung, die den -Tupeln in C # und Struktupeln in F # zugrunde liegt.

Abgesehen von der über die Sprachsyntax erstellten Methode können sie am einfachsten über die ValueTuple.Create factory-Methoden ..__ erstellt werden. Die System.ValueTuple-Typen unterscheiden sich von den System.Tuple-Typen dadurch:

  • sie sind eher Strukturen als Klassen,
  • sie sind eher veränderlich als schreibgeschützt und
  • ihre Mitglieder (wie Artikel1, Artikel2 usw.) sind Felder und nicht Eigenschaften.

Mit der Einführung dieses Typs und des C # 7.0-Compilers können Sie problemlos schreiben

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

Und geben Sie zwei Werte aus einer Methode zurück:

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

Im Gegensatz zu System.Tuple können Sie seine Mitglieder aktualisieren (veränderlich), da es sich um öffentliche Lese- und Schreibfelder handelt, denen sinnvolle Namen gegeben werden können:

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

Abgesehen von den obigen Kommentaren besteht ein unglücklicher Umstand von ValueTuple darin, dass als Werttyp die benannten Argumente beim Kompilieren in IL gelöscht werden, sodass sie zur Laufzeit nicht zur Serialisierung verfügbar sind.

ihre süßen benannten Argumente enden immer noch als "Item1", "Item2" usw., wenn sie über z. Json.NET.

3
ZenSquirrel

Late-Join, um diese beiden Faktoiden kurz zu erläutern:

  • sie sind eher Strukturen als Klassen
  • sie sind eher veränderlich als nur lesbar

Man könnte denken, dass es einfach ist, die Wertetupel in großen Mengen zu ändern:

 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;

Jemand könnte versuchen, dies so zu umgehen:

 // 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'

Der Grund für dieses skurrile Verhalten ist, dass die Wertetupel genau auf Werten basieren (structs) und der Aufruf .Select (...) daher nicht auf den Originalen, sondern auf geklonten Strukturen funktioniert. Um dies zu beheben, müssen wir auf Folgendes zurückgreifen:

 // 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

Alternativ könnte man natürlich auch den einfachen Ansatz ausprobieren:

   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

Hoffe, dies hilft jemandem, der sich abmüht, aus Listen-gehosteten Wert-Tupeln Köpfe mit Schwänzen zu machen.

2
XDS

Dies ist eine ziemlich gute Zusammenfassung dessen, was es ist und seine Begrenzung

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

0
nick-s