C#-TupeltypenC# Tuple types

C#-Tupel sind Typen, die Sie mithilfe einer einfachen Syntax definieren.C# Tuples are types that you define using a lightweight syntax. Zu den Vorteilen gehören eine einfachere Syntax, Regeln für auf Zahlen basierte Umwandlungen (sogenannte „Kardinalitäten“) und Typen von Elementen sowie konsistente Regeln für Kopien und Aufgaben.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 and assignments. Dafür unterstützen Tupel nicht einige der objektorientierten Idiome, die der Vererbung zugeordnet werden.As a tradeoff, Tuples do not support some of the object oriented idioms associated with inheritance. Sie erhalten im Abschnitt über Tupel im Thema Neues in C# 7 einen Überblick.You can get an overview in the section on Tuples in the What's new in C# 7 topic.

In diesem Thema lernen Sie die Sprachregeln, die Tupel in C# 7 steuern, verschiedene Verwendungsarten und einen ersten Leitfaden über das Arbeiten mit Tupel.In this topic, you'll learn the language rules governing Tuples in C# 7, different ways to use them, and initial guidance on working with Tuples.

Hinweis

Die neuen Tupeleigenschaften benötigen die ValueTuple-Typen.The new tuples features require the ValueTuple types. Sie müssen das NuGet-Paket System.ValueTuple hinzufügen, um es auf Plattformen zu verwenden, die keine Typen enthalten.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

Dies ist ähnlich wie bei anderen Sprachfunktionen, die auf im Framework übermittelten Typen basieren.This is similar to other language features that rely on types delivered in the framework. Beispiele hierfür sind async und await, die auf der INotifyCompletion-Schnittstelle basieren, und LINQ, das auf IEnumerable<T> basiert.Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. Allerdings ändert sich der Übermittlungsmechanismus, da .NET plattformunabhängiger wird.However, the delivery mechanism is changing as .NET is becoming more platform independent. .NET Framework wird möglicherweise nicht immer im gleichen Rhythmus wie der Sprachcompiler ausgeliefert.The .NET Framework may not always ship on the same cadence as the language compiler. Wenn neue Sprachfunktionen auf neuen Typen basieren, sind diese Typen als NuGet-Pakete bei der Auslieferung der Sprachfunktionen verfügbar.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. Da diese neuen Typen dem standardmäßigen .NET API hinzugefügt und als Teil des Frameworks bereitgestellt werden, wird die Anforderung des NuGet-Pakets entfernt.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.

Beginnen wir mit den Gründen für das Hinzufügen von neuer Unterstützung für Tupel.Let's start with the reasons for adding new Tuple support. Methoden geben ein einzelnes Objekt zurück.Methods return a single object. Mit Tupel können Sie mehrere Werte einfacher in das einzelne Objekt packen.Tuples enable you to package multiple values in that single object more easily.

.NET Framework hat bereits generische Tuple-Klassen.The .NET Framework already has generic Tuple classes. Diese Klassen hatten jedoch zwei wesentliche Einschränkungen.These classes, however, had two major limitations. Zum einen haben die Tuple-Klassen ihre Eigenschaften als Item1, Item2 usw. bezeichnet.For one, the Tuple classes named their properties Item1, Item2, and so on. Diese Namen enthalten keine semantischen Informationen.Those names carry no semantic information. Mit diesen Tuple-Typen kann nicht die Bedeutung der einzelnen Eigenschaften kommuniziert werden.Using these Tuple types does not enable communicating the meaning of each of the properties. Die neuen Sprachfeatures ermöglichen Ihnen, semantisch aussagekräftige Namen für die Elemente eines Tupels zu deklarieren und zu verwenden.The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

Ein weiterer Aspekt ist, dass die Tuple-Klassen Verweistypen sind.Another concern is that the Tuple classes are reference types. Das Verwenden von einem der Tuple-Typen bedeutet, dass Objekte zugeordnet werden.Using one of the Tuple types means allocating objects. Auf dem langsamsten Pfad kann das einen messbaren Einfluss auf die Leistung Ihrer Anwendungen haben.On hot paths, this can have a measurable impact on your application's performance. Deshalb nutzt die Sprachunterstützung für Tupels die neuen ValueTuple-Strukturen.Therefore, the language support for tuples leverages the new ValueTuple structs.

Sie können class oder struct zum Tragen von mehreren Elementen erstellen, um diese Mängel zu vermeiden.To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Leider bedeutet das mehr Arbeit für Sie, und es verdeckt Ihre Entwurfsabsicht.Unfortunately, that's more work for you, and it obscures your design intent. Durch das Erstellen von struct und class wird angedeutet, dass Sie einen Typ sowohl mit Daten als auch mit Verhalten definieren.Making a struct or class implies that you are defining a type with both data and behavior. In vielen Fällen möchten Sie einfach mehrere Werte in ein einzelnes Objekt speichern.Many times, you simply want to store multiple values in a single object.

Die Sprachfunktionen und die generischen ValueTuple-Strukturen setzen die Regel durch, dass Sie nicht jedes beliebige Verhalten (Methoden) den Tupeltypen hinzufügen können.The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. Alle ValueTuple-Typen sind veränderbare Strukturen.All the ValueTuple types are mutable structs. Jedes Memberfeld ist ein öffentliches Feld.Each member field is a public field. Das macht sie sehr einfach.That makes them very lightweight. Allerdings bedeutet dies, dass Tupel nicht dort verwendet werden dürfen, wo Unveränderlichkeit wichtig ist.However, that means tuples should not be used where immutability is important.

Tupel sind sowohl einfachere als auch flexiblere Datencontainer als class- und struct-Typen.Tuples are both simpler and more flexible data containers than class and struct types. Betrachten wir diese Unterschiede.Let's explore those differences.

Benannte und unbenannte TupelNamed and unnamed tuples

Die ValueTuple-Struktur hat Felder mit den Namen Item1, Item2, Item3 usw., die den Eigenschaften ähnlich sind, die in den vorhandenen Tuple-Typen definiert wurden.The ValueTuple struct has fields named Item1, Item2, Item3 and so on, similar to the properties defined in the existing Tuple types. Diese Namen sind die einzigen Namen, die Sie für unbenannte Tupel verwenden können.These names are the only names you can use for unnamed tuples. Wenn Sie einem Tupel keine alternativen Feldnamen bereitstellen, haben Sie einen unbenannten Tupel erstellt:When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

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

Das Tupel im vorherigen Beispiel wurde mithilfe von Literalkonstanten initialisiert und verfügt nicht über Elementnamen, die mithilfe von Projektionen für Tupel-Feldnamen in C# 7.1 erstellt wurden.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.

Sie können jedoch bei der Initialisierung eines Tupels neue Spracheigenschaften verwenden, die jedem Feld bessere Namen geben.However, when you initialize a tuple, you can use new language features that give better names to each field. Dadurch wird ein benannter Tupel erstellt.Doing so creates a named tuple. Benannte Tupel haben immer noch Elemente mit den Namen Item1, Item2, Item3 usw.Named tuples still have elements named Item1, Item2, Item3 and so on. Sie haben jedoch auch Synonyme für alle Elemente, die Sie benannt haben.But they also have synonyms for any of those elements that you have named. Sie erstellen einen benannten Tupel, indem Sie den Namen für jedes Element angeben.You create a named tuple by specifying the names for each element. Eine Möglichkeit ist es, die Namen als Teil der Initialisierung des Tupel anzugeben:One way is to specify the names as part of the tuple initialization:

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

Diese Synonyme werden vom Compiler und der Sprache verarbeitet, sodass Sie benannte Tupel effektiv verwenden können.These synonyms are handled by the compiler and the language so that you can use named tuples effectively. IDEs und Editoren können die semantischen Namen mithilfe der Roslyn-APIs lesen.IDEs and editors can read these semantic names using the Roslyn APIs. Dadurch können Sie die Elemente eines benannten Tupels durch diese semantischen Namen an einer beliebigen Stelle in der gleichen Assembly verweisen.This enables you to reference the elements of a named tuple by those semantic names anywhere in the same assembly. Der Compiler ersetzt die Namen, die Sie definiert haben, mit Item*-Äquivalenten, wenn die Compilerausgabe erzeugt wird.The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. Die kompilierte Microsoft Intermediate Language (MSIL) enthält nicht die Namen, die Sie diesen Elementen gegeben haben.The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

Ab C# 7.1 können die Feldnamen für ein Tupel von den Variablen bereitgestellt werden, die zum Initialisieren des Tupels verwendet werden.Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. Diese werden als Tupel-Projektionsinitialisierer bezeichnet.This is referred to as tuple projection initializers. Der folgende Code erstellt ein Tupel namens accumulation mit den Elementen count (ein integer-Typ Zahl) und sum (ein Double-Typ).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);

Der Compiler muss diese Namen kommunizieren, die Sie für Tupel erstellt haben, die von öffentlichen Methoden oder Eigenschaften zurückgegeben wurden.The compiler must communicate those names you created for tuples that are returned from public methods or properties. In diesen Fällen fügt der Compiler ein Attribut TupleElementNamesAttribute zur Methode hinzu.In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. Dieses Attribut enthält eine TransformNames-Listeneigenschaft, die die Namen für jedes der Elemente im Tupel enthält.This attribute contains a TransformNames list property that contains the names given to each of the elements in the Tuple.

Hinweis

Entwicklungstools wie Visual Studio lesen auch diese Metadaten und stellen IntelliSense und andere Funktionen bereit, die die Feldnamen der Metadaten verwenden.Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

Es ist wichtig, diese zugrunde liegenden Grundlagen der neuen Tupel sowie den ValueTuple-Typ zu verstehen, um die Regeln für das gegenseitige Zuweisen von benannten Tupel zu verstehen.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.

Tupel-ProjektionsinitialisiererTuple projection initializers

Im allgemeinen funktionieren Tupel-Projektionsinitialisierer, indem die Variablen- oder Feldnamen von der rechten Seite der Initialisierungsanweisung eines Tupels verwendet werden.In general, tuple projection initializers work by using the variable or field names from the right-hand side of a tuple initialization statement. Wenn ein expliziter Name vergeben wird, hat dieser vor jedem projizierten Namen Vorrang.If an explicit name is given, that takes precedence over any projected name. Im folgenden Initialisierer sind die Elemente beispielsweise explicitFieldOne und explicitFieldTwo, nicht localVariableOne und 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);

Für jedes Feld ohne expliziten Namen wird ein zutreffender, impliziter Name projiziert.For any field where an explicit name is not provided, an applicable implicit name will be projected. Beachten Sie, dass es weder explizit noch implizit nötig ist, semantische Namen bereitzustellen.Note that there is no requirement to provide semantic names, either explicitly or implicitly. Die folgenden Initialisierer müssen Feldnamen Item1, dessen Wert 42 und StringContent, deren Wert "Die Antwort auf alle Elemente" ist:The following initializer will have 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);

Es gibt zwei Bedingungen, unter denen die Feldnamen von Kandidaten nicht auf das Tupelfeld projiziert werden:There are two conditions where candidate field names are not projected onto the tuple field:

  1. Wenn es sich beim Namen des Kandidaten um einen reservierten Tupelnamen handelt.When the candidate name is a reserved tuple name. Beispiele dafür sind Item3, ToString oder Rest.Examples include Item3, ToString or Rest.
  2. Wenn es sich beim Namen des Kandidaten um das Duplikat des expliziten oder impliziten Feldnamens eines anderen Tupels handelt.When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

Durch diese Bedingungen wird Mehrdeutigkeit vermieden.These conditions avoid ambiguity. Diese Namen würden eine Mehrdeutigkeit verursachen, wenn sie als Feldnamen für das Feld eines Tupels verwendet würden.These names would cause an ambiguity if they were used as the field names for a field in a tuple. Keine dieser Bedingungen verursacht Kompilierzeitfehler.Neither of these conditions cause compile time errors. Stattdessen werden für die Elemente, die nicht über projizierte Namen verfügen, keine semantischen Namen projiziert.Instead, the elements without projected names do not have semantic names projected for them. In den folgenden Beispielen werden diese Bedingungen veranschaulicht: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 'Item`.

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

Diese Situationen verursachen keine Compilerfehler, da diese eine grundlegende Änderung für mit C# 7.0 geschriebenen Code darstellen würden, bei dem die Projektionen für Tupel-Feldnamen noch nicht verfügbar waren.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.

Zuweisung und TupelAssignment and tuples

Die Sprache unterstützt die Zuweisung zwischen Tupeltypen, die über die gleiche Anzahl von Elementen und implizite Konvertierungen für die Typen für jedes dieser Elemente verfügen.The language supports assignment between tuple types that have the same number of elements and implicit conversions for the types for each of those elements. Andere Konvertierungen gelten nicht für Zuordnungen.Other conversions are not considered for assignments. Sehen wir uns die Arten von Zuweisungen an, die zwischen Tupeltypen zulässig sind.Let's look at the kinds of assignments that are allowed between tuple types.

Berücksichtigen Sie diese Variablen, die in den folgenden Beispielen verwendet werden: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");

Die ersten zwei Variablen (unnamed und anonymous) haben keine semantischen Namen für die Elemente bereitgestellt.The first two variables, unnamed and anonymous do not have semantic names provided for the elements. Die Feldnamen lauten Item1 und Item2.The field names are Item1 and Item2. Die letzten zwei Variablen (named und differentName) haben semantische Namen für die Elemente angegeben.The last two variables, named and differentName have semantic names given for the elements. Beachten Sie, dass diese zwei Tupel unterschiedliche Namen für die Elemente besitzen.Note that these two tuples have different names for the elements.

Alle vier dieser Tupel haben die gleiche Anzahl von Elementen (als „Kardinalität“ bezeichnet), und die Typen dieser Elemente sind identisch.All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. Daher funktionieren alle Zuweisungen: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;

Beachten Sie, dass die Namen der Tupel nicht zugewiesen sind.Notice that the names of the tuples are not assigned. Die Werte der Elemente werden nach der Reihenfolge der Elemente im Tupel zugewiesen.The values of the elements are assigned following the order of the elements in the tuple.

Tupel mit unterschiedlichen Typen oder mit einer unterschiedlichen Anzahl von Elementen sind nicht zuweisbar: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;

Tupel als MethodenrückgabewertTuples as method return values

Einer der häufigsten Verwendungszwecke für Tupel ist der Methodenrückgabewert.One of the most common uses for Tuples is as a method return value. Lassen Sie uns ein ausführliches Beispiel ansehen.Let's walk through one example. Betrachten Sie diese Methode, die die Standardabweichung für eine Sequenz von Zahlen berechnet: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;
}

Hinweis

In diesem Beispiel wird die unkorrigierte Beispielstandardabweichung berechnet.These examples compute the uncorrected sample standard deviation. Die korrigierte Formel der Beispielstandardabweichung würde die Summe der Differenzen im Quadrat vom Mittelwert mithilfe von (N-1) anstatt N teilen, wie es die Erweiterungsmethode Average macht.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. Weitere Informationen zu den Unterschieden zwischen diesen Formeln für die Standardabweichung finden Sie in einem Statistiktext.Consult a statistics text for more details on the differences between these formulas for standard deviation.

Dies entspricht der lehrbuchmäßigen Formel für die Standardabweichung.This follows the textbook formula for the standard deviation. Die richtige Antwort wird generiert, aber es ist eine sehr ineffiziente Implementierung.It produces the correct answer, but it's a very inefficient implementation. Diese Methode listet die Sequenz zweimal auf: Einmal, um den Mittelwert zu erzeugen und einmal, um den Durchschnitt des Quadrats vom Unterschied des Durchschnitts zu erzeugen.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. (Beachten Sie, dass LINQ-Abfragen verzögert ausgewertet werden. Daher kann die Berechnung der Unterschiede vom Mittelwert und des Durchschnitts dieser Unterschiede nur eine Enumeration erzeugen.)(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.)

Es gibt eine alternative Formel, die eine Standardabweichung anhand einer Enumeration der Sequenz berechnet.There is an alternative formula that computes standard deviation using only one enumeration of the sequence. Diese Berechnung erzeugt zwei Werte, da sie die Sequenz auflistet: Die Summe aller Elemente in der Sequenz und die Summe jedes Werts im Quadrat: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);
}

Diese Version listet die Sequenz genau einmal auf.This version enumerates the sequence exactly once. Der Code kann jedoch nicht wirklich wieder verwendet werden.But, it's not very reusable code. Während Sie arbeiten, werden Sie sehen, dass viele unterschiedliche statistische Berechnungen die Anzahl von Elementen in der Sequenz, die Summe der Sequenzen und die Summe der Quadrate der Sequenz verwenden.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. Gestalten Sie diese Methode um und schreiben Sie eine Hilfsmethode, die alle diese Werte erzeugt.Let's refactor this method and write a utility method that produces all three of those values.

Dafür sind Tupel sehr nützlich.This is where tuples come in very useful.

Wir aktualisieren diese Methoden, sodass die drei Werte, die während der Enumeration berechnet werden, in einem Tupel gespeichert werden.Let's update this method so the three values computed during the enumeration are stored in a tuple. Dadurch wird diese Version erstellt: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);
}

Refactoring-Unterstützung für Visual Studio erleichtert es, um die Funktionalität für die Core-Statistik in eine private Methode extrahieren.Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. Das gibt Ihnen eine private static-Methode, die den Tupeltyp mit den drei Werten von Sum, SumOfSquares und Count zurückgibt: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 = ComputeSumsAnSumOfSquares(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) ComputeSumsAnSumOfSquares(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;
}

Die Sprache aktiviert einige weitere Optionen, die Sie verwenden können, wenn Sie ein Paar schnelle manuelle Bearbeitungen vornehmen möchten.The language enables a couple more options that you can use, if you want to make a few quick edits by hand. Sie können zuerst die Deklaration var verwenden, um das Tupelergebnis vom Methodenaufruf ComputeSumAndSumOfSquares zu initialisieren.First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. Sie können auch drei diskrete Variablen in der Methode ComputeSumAndSumOfSquares erstellen.You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. Die endgültige Version ist wie folgt:The final version is below:

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

Die endgültige Version kann für jede Methode, die diese drei Werte oder einen Teil davon benötigt, verwendet werden.This final version can be used for any method that needs those three values, or any subset of them.

Die Sprache unterstützt andere Optionen, indem sie die Namen der Elemente in diesen Methoden verwaltet, die Tupel zurückgeben.The language supports other options in managing the names of the elements in these tuple-returning methods.

Sie können die Feldnamen aus der Deklaration des Rückgabewerts entfernen und einen unbenannten Tupel zurückgeben: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);
}

Sie müssen die Felder dieses Tupels als Item1, Item2 und Item3 behandeln.You must address the fields of this tuple as Item1, Item2, and Item3. Es wird empfohlen, dass Sie den Tupelelementen, die von Methoden zurückgegeben werden, semantische Namen bereitstellen.It's recommended that you provide semantic names to the elements of tuples returned from methods.

Eine anderes Idiom, in dem Tupel sehr nützlich sein könnten, ist beim Verfassen von LINQ-Abfragen, bei denen das endgültige Ergebnis eine Projektion ist, die einige – aber nicht alle – Eigenschaften der ausgewählten Objekte enthält.Another idiom where tuples can be very useful is when you are authoring LINQ queries where the final result is a projection that contains some, but not all, of the properties of the objects being selected.

Sie würden normalerweise die Abfrageergebnisse in eine Sequenz von Objekten projizieren, die ein anonymer Typ waren.You would traditionally project the results of the query into a sequence of objects that were an anonymous type. Das stellte viele Einschränkungen dar, hauptsächlich, weil anonyme Typen nicht bequem im Rückgabetyp für eine Methode benannt werden konnten.That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. Alternativen, in denen object und dynamic als Typ des Ergebnisses verwende wurden, gingen mit erheblichen Leistungseinbußen einher.Alternatives using object or dynamic as the type of the result came with significant performance costs.

Das Zurückgeben einer Sequenz eines Tupeltyps ist einfach, und die Namen und Typen der Elemente sind zur Kompilierzeit und über die IDE-Tools verfügbar.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. Betrachten Sie z B. eine ToDo-Anwendung.For example, consider a ToDo application. Sie könnten eine Klassen vielleicht so ähnlich wie folgt definieren, um einen einzelnen Eintrag in der ToDo-Liste darzustellen: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; }    
}

Ihre mobile Anwendung unterstützt vielleicht ein kompaktes Format der aktuellen ToDo-Elemente, die nur den Titel anzeigen.Your mobile applications may support a compact form of the current ToDo items that only displays the title. LINQ-Abfragen würden eine Projektion erstellen, die nur die ID und den Titel enthält.That LINQ query would make a projection that includes only the ID and the title. Eine Methode, die eine Tupelsequenz zurückgibt, drückt den Entwurf sehr gut aus:A method that returns a sequence of tuples expresses that design very well:

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

Hinweis

In C# 7.1 ermöglichen Tupelprojektionen Ihnen das Erstellen benannter Tupel mithilfe von Elementen, ähnlich wie beim Benennen von Eigenschaften bei anonymen Typen.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. Im obigen Code erstellt die select-Anweisung in der Abfrageprojektionen ein Tupel, das die Elemente ID und Title besitzt.In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

Das benannte Tupel kann Teil der Signatur sein.The named tuple can be part of the signature. Es lässt den Compiler und IDE-Tools statisch bereitstellen, indem überprüft wird, ob Sie das Ergebnis richtig verwenden.It lets the compiler and IDE tools provide static checking that you are using the result correctly. Das benannte Tupel trägt auch die statischen Typinformationen, sodass keine teuren Laufzeitfunktionen wie Reflektion oder dynamische Bindung verwendet werden müssen, um mit den Ergebnissen zu arbeiten.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.

DekonstruktionDeconstruction

Sie können alle Elemente in einem Tupel entpacken, indem das Tupel, das von einer Methode zurückgegeben wurde, dekonstruiert wird.You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. Es gibt drei verschiedene Ansätze zum Zerlegen von Tupeln aus.There are three different approaches to deconstructing tuples. Zuerst können Sie ausdrücklich den Typ von jedem Feld in Klammern deklarieren, um diskrete Variablen für jedes der Elemente im Tupel zu erstellen: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);
}

Sie können auch typisierte Variablen für jedes Feld in ein Tupel deklarieren, indem das Schlüsselwort var außerhalb der Klammern verwendet wird: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);
}

Das Verwenden des Schlüsselworts var mit beliebigen oder allen Variablendeklarationen in den Klammern ist auch zulässig.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);

Beachten Sie, dass Sie einen bestimmten Typ außerhalb der Klammern verwenden können, auch wenn jedes Feld im Tupel den selben Typ hat.Note that you cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

Sie können Tupeln mit vorhandenen Deklarationen sowie löschen: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);
}

Warnung

Sie können keine vorhandene Deklarationen mit Deklarationen innerhalb der Klammern kombinieren.You cannot mix existing declarations with declarations inside the parentheses. Folgendes ist z. B. nicht zulässig: (var x, y) = MyMethod();.For instance, the following is not allowed: (var x, y) = MyMethod();. Dadurch wird der Fehler CS8184 generiert, da x wird innerhalb der Klammern deklariert und y ist bereits an anderer Stelle deklariert.This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

Dekonstruieren von benutzerdefinierten TypenDeconstructing user defined types

Jeder Tupeltyp kann dekonstruiert werden, wie oben gezeigt wurde.Any tuple type can be deconstructed as shown above. Es ist genau so einfach, die Dekonstruktion in beliebigen benutzerdefinierten Typen (Klassen, Strukturen oder sogar Oberflächen) zu aktivieren.It's also easy to enable deconstruction on any user defined type (classes, structs, or even interfaces).

Der Autor des Typs kann eine oder mehrere Deconstruct-Methoden definieren, die einer beliebigen Anzahl von out-Variablen Werte zuweisen, die die Datenelemente darstellen, die den Typ ausmachen.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. Der folgende Person-Typ definiert z.B. eine Deconstruct-Methode, die ein Personenobjekt in die Elemente dekonstruiert, die dem Vor- und Nachnamen entsprechen: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;
    }
}

Die Dekonstruktionsmethode ermöglicht die Zuordnung von einer Person zu zwei Zeichenfolgen, die die Eigenschaften FirstName und LastName darstellen: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;

Sie können Dekonstruktion selbst für Typen aktivieren, die Sie nicht geschrieben haben.You can enable deconstruction even for types you did not author. Die Deconstruct-Methode kann eine Erweiterungsmethode sein, die die zugänglichen Datenmember eines Objekts entpackt.The Deconstruct method can be an extension method that unpackages the accessible data members of an object. Das folgende Beispiel zeigt einen Student-Typ, der vom Person-Typ abgeleitet wurde, sowie eine Erweiterungsmethode, die ein Student in drei Variablen dekonstruiert, die den FirstName, LastName und GPA darstellen: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;
    }
}

Ein Student-Objekt hat jetzt zwei zugängliche Deconstruct-Methoden: Die Erweiterungsmethode, die für Student-Typen deklariert wurde, und die Member des Person-Typs.A Student object now has two accessible Deconstruct methods: the extension method declared for Student types, and the member of the Person type. Beide befinden sich im Geltungsbereich, womit ein Student entweder in zwei oder drei Variablen dekonstruiert werden kann.Both are in scope, and that enables a Student to be deconstructed into either two variables or three. Wenn Sie einem Studenten drei Variablen zuweisen, werden der Vorname, der Nachname sowie das GPA zurückgegeben.If you assign a student to three variables, the first name, last name, and GPA are all returned. Wenn Sie einem Studenten zwei Variablen zuweisen, werden nur Vor- und Nachname zurückgegeben.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;

Sie sollten sehr vorsichtig mit dem Definieren von mehreren Deconstruct-Methoden in einer Klasse oder einer Klassenhierarchie sein.You should be very careful defining multiple Deconstruct methods in a class or a class hierarchy. Mehrere Deconstruct-Methoden, die die gleiche Anzahl von out-Parameter haben, können schnell zu Mehrdeutigkeiten führen.Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. Aufrufer können dann vielleicht nicht die gewünschte Deconstruct-Methode aufrufen.Callers may not be able to easily call the desired Deconstruct method.

In diesem Beispiel besteht eine minimale Wahrscheinlichkeit für einen mehrdeutigen Aufruf, weil die Deconstruct-Methode für Person zwei Ausgabeparameter besitzt, und die Deconstruct-Methode für Student drei besitzt.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.

SchlussfolgerungConclusion

Die neue Sprach- und Bibliotheksunterstützung für benannte Tupel vereinfacht es, mit Entwürfen zu arbeiten, die Datenstrukturen verwenden, die mehrere Elemente definieren, aber keine Verhalten definieren, wie es Klassen und Strukturen tun.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. Die Verwendung von Tupeln ist für diese Typen einfach und präzise.It's easy and concise to use tuples for those types. Sie erhalten alle Vorteile der Überprüfung von statischen Typen, ohne Typen mithilfe der ausführlicheren class- oder struct-Syntax schreiben zu müssen.You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. Dennoch sind sie für Hilfsmethoden, die private oder internal sind, am nützlichsten.Even so, they are most useful for utility methods that are private, or internal. Erstellen Sie benutzerdefinierte Typen, entweder Typen class oder struct, wenn Ihre öffentlichen Methoden einen Wert mit mehreren Elementen zurückgeben.Create user defined types, either class or struct types when your public methods return a value that has multiple elements.