Freigeben über


Tupeln und Dekonstruktion

Tipp

Neu bei der Entwicklung von Software? Beginnen Sie mit den Lernprogrammen " Erste Schritte ". Wenn Sie mehrere Werte aus einer Methode oder Gruppe zurückgeben müssen, ohne einen benannten Typ zu definieren, treten Tupel auf.

Haben Sie Erfahrung in einer anderen Sprache? C#-Tupel sind Werttypen, die mit Tupeln in Python oder Swift vergleichbar sind, jedoch mit optionalen benannten Elementen und vollständiger Dekonstruktionsunterstützung. Trennen Sie die Dekonstruktions - und Gleichheitsabschnitte für C#-spezifische Muster.

Ein Tupel gruppiert mehrere Werte in einer einzelnen, einfachen Struktur, ohne dass Sie einen benannten Typ definieren müssen. Tupel sind Werttypen, die Sie im laufenden Text deklarieren, von Methoden zurückgeben und in einzelne Variablen dekonstruieren können. Verwenden Sie Tupel, wenn Sie eine schnelle, temporäre Gruppierung verwandter Werte benötigen. Wenn Sie beispielsweise mehrere Ergebnisse aus einer Methode zurückgeben oder ein Koordinatenpaar speichern.

Im folgenden Beispiel wird ein Tupel mit benannten Elementen erstellt und nach Namen auf jedes Element zugegriffen:

var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321

Tupel funktionieren gut für kurzlebige Gruppierungen, bei denen das Definieren einer Klasse, einer Struktur oder eines Datensatzes unnötige Zeremonien hinzufügen würde. Für langlebige Domänenkonzepte oder Typen mit Verhalten bevorzugen Sie Datensätze, Klassen oder Strukturen. Für einen Vergleich, wann welche verwendet werden soll, siehe "Welcher Typ gewählt werden soll".

Deklarieren und Initialisieren eines Tupels

Deklarieren Sie ein Tupel, indem Sie die Elementtypen in Klammern auflisten. Sie können optional jedes Element benennen, um den Code besser lesbar zu machen:

// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");

// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");

// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");

Wenn Sie keine Namen angeben, verwenden Elemente Standardnamen Item1, Item2usw. Benannte Elemente machen Ihr Code selbstdokumentierend, ohne dass eine separate Typdefinition erforderlich ist.

Abgeleitete Elementnamen

Der Compiler leitet Elementnamen aus den Variablennamen oder Eigenschaftennamen ab, die Sie zum Initialisieren des Tupels verwenden. Dieses Feature vermeidet Redundanz, wenn die Namen übereinstimmen:

var name = "Carol";
var age = 28;

// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28

Abgeleitete Bezeichner halten Ihren Code prägnant. Wenn Sie einen anderen Elementnamen benötigen, geben Sie ihn explizit an.

Zurückgeben mehrerer Werte aus einer Methode

Eine der am häufigsten verwendeten Verwendungsmöglichkeiten für Tupel ist das Zurückgeben mehrerer Werte aus einer Methode. Statt eine Klasse zu definieren oder Parameter zu verwenden out , geben Sie ein Tupel mit benannten Elementen zurück:

static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
    var min = values.Min();
    var max = values.Max();
    var avg = values.Average();
    return (min, max, avg);
}

Benannte Tupelelemente machen die Rückgabewerte sowohl an der Aufrufstelle als auch in der Methodensignatur lesbar. Der Aufrufer kann nach Namen auf jeden Wert zugreifen, ohne die Positionsreihenfolge merken zu müssen.

Deconstruct Tupel

Deconstruction entpackt die Elemente eines Tupels in separate Variablen in einer einzelnen Anweisung. Sie können Tupel auf verschiedene Arten dekonstruieren:

var point = (X: 3, Y: 7);

// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");

// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");

// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");

// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");

Die Dekonstruktion ist besonders nützlich, wenn Sie ein Tupel aus einem Methodenaufruf erhalten und sofort mit den einzelnen Werten arbeiten müssen.

Sie können Tupel direkt in foreach-Schleifen dekonstruieren, was das Durchlaufen von Auflistungen gruppierter Werte kompakt macht.

List<(string Name, int Score)> results =
[
    ("Alice", 92),
    ("Bob", 87),
    ("Carol", 95)
];

foreach (var (name, score) in results)
{
    Console.WriteLine($"{name}: {score}");
}

Wenn Sie nicht jedes Element benötigen, verwenden Sie anstelle jedes Werts, den Sie ignorieren möchten, einen Verwerfen (_). Verwenden Sie eine separate _ für jede verworfene Position:

List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15

Weitere Informationen zum Verwenden von Verworfen in verschiedenen Kontexten finden Sie unter "Verwerfen".

Tupelgleichheit

Sie können Tupel mit == und != vergleichen. Diese Operatoren vergleichen jedes Element in der Reihenfolge, sodass zwei Tupel gleich sind, wenn alle entsprechenden Elemente gleich sind:

var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);

Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False

// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True

Die Tupelgleichheit verwendet den Operator ==, der für jeden Elementtyp definiert ist, was bedeutet, dass der Vergleich für Zeichenfolgen, Zahlen und andere Typen, für die der Operator == definiert ist, ordnungsgemäß funktioniert. Elementnamen wirken sich nicht auf die Gleichheit aus – nur Werte und Positionen sind wichtig.

Nicht destruktive Mutation mit with

Der with Ausdruck erstellt eine Kopie eines Tupels mit einem oder mehreren geänderten Elementen, sodass das Original unverändert bleibt:

var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };

Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99

Dieses Muster ist nützlich, wenn Sie eine Variation eines vorhandenen Tupels wünschen, ohne das Original zu ändern. Der with Ausdruck funktioniert bei Tupeln genauso wie bei Datensätzen.

Tupel in Wörterbüchern und Abfragen

Tupel sind praktische Werte in einem Wörterbuch, wenn Sie einem Schlüssel mehrere Datenstücke zuordnen müssen.

var sizeChart = new Dictionary<string, (int Min, int Max)>
{
    ["Small"] = (0, 50),
    ["Medium"] = (51, 100),
    ["Large"] = (101, 200)
};

if (sizeChart.TryGetValue("Medium", out var range))
{
    Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100

Tupel funktionieren auch als Wörterbuchschlüssel, sodass Sie einen zusammengesetzten Schlüssel erhalten, ohne einen benutzerdefinierten Typ zu definieren. Da Tupel strukturelle Gleichheit implementieren, passen Suchvorgänge mit den kombinierten Werten aller Elemente zusammen:

var grid = new Dictionary<(int Row, int Column), string>
{
    [(0, 0)] = "Origin",
    [(1, 3)] = "Sensor A",
    [(2, 5)] = "Sensor B"
};

var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
    Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A

Dieses Muster vermeidet die Notwendigkeit einer separaten Klasse für einfache Mehrfachschlüsselsuche oder Schlüssel-zu-Mehrwert-Zuordnungen.

Tupel gegenüber anonymen Typen

Tupel sind die bevorzugte Wahl, wenn Sie eine leichtgewichtige, unbenannte Datenstruktur benötigen. Anonyme Typen bleiben für Ausdrucksstrukturszenarien und für Code verfügbar, der Referenztypen erfordert. Tupel bieten jedoch eine bessere Leistung, Unterstützung der Dekonstruktion und flexiblere Syntax. Weitere Informationen zu anonymen Typen finden Sie unter Auswahl zwischen anonymen und Tupeltypen.

Siehe auch