Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Das Tupelfeature bietet eine präzise Syntax, um mehrere Datenelemente in einer einfachen Datenstruktur zu gruppieren.
Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie erste Dokumentation für Features in der öffentlichen Vorschau für die kommende Sprachversion.
In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.
Tipp
Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.
Im folgenden Beispiel wird veranschaulicht, wie Sie eine Tupelvariable deklarieren, initialisieren und dafür auf die zugehörigen Datenmember zugreifen können:
(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.
(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.
Wie im vorherigen Beispiel gezeigt, geben Sie zum Definieren eines Tupeltyps die Typen aller zugehörigen Datenmember und optional die Feldnamen an. Sie können keine Methoden in einem Tupeltyp definieren, aber Sie können die von .NET bereitgestellten Methoden verwenden. Dies wird im folgenden Beispiel veranschaulicht:
(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.
Tupeltypen unterstützen die Gleichheitsoperatoren== und !=. Weitere Informationen finden Sie im Abschnitt Tupelgleichheit.
Tupeltypen sind Werttypen, und Tupelelemente sind öffentliche Felder. Dieses Design macht Tupel änderbare Werttypen.
Sie können Tupel mit einer beliebig großen Anzahl von Elementen definieren:
var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26); // output: 26
Anwendungsfälle für Tupel
Einer der am häufigsten verwendeten Anwendungsfälle für Tupel ist ein Methodenrückgabetyp. Anstatt Methodenparameter zu definierenout, führt die Gruppenmethode zu einem Tupel-Rückgabetyp, wie das folgende Beispiel zeigt:
int[] xs = new int[] { 4, 7, 9 };
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9
int[] ys = new int[] { -9, 0, 67, 100 };
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100
(int min, int max) FindMinMax(int[] input)
{
if (input is null || input.Length == 0)
{
throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
}
// Initialize min to MaxValue so every value in the input
// is less than this initial value.
var min = int.MaxValue;
// Initialize max to MinValue so every value in the input
// is greater than this initial value.
var max = int.MinValue;
foreach (var i in input)
{
if (i < min)
{
min = i;
}
if (i > max)
{
max = i;
}
}
return (min, max);
}
Wie Sie im obigen Beispiel sehen, können Sie direkt mit der zurückgegebenen Tupelinstanz arbeiten oder sie in separate Variablen dekonstruieren.
Sie können Tupeltypen auch anstelle von anonymen Typen verwenden, z. B. in LINQ-Abfragen. Weitere Informationen finden Sie unter Auswählen zwischen anonymen Typen und Tupeltypen.
Verwenden Sie in der Regel Tupel, um lose verwandte Datenelemente zu gruppieren. Erwägen Sie bei öffentlichen APIs, den Typ als Klasse oder Struktur zu definieren.
Tupelfeldnamen
Sie geben Tupelfeldnamen explizit in einem Tupelinitialisierungsausdruck oder in der Definition eines Tupeltyps an, wie das folgende Beispiel zeigt:
var t = (Sum: 4.5, Count: 3);
Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");
(double Sum, int Count) d = (4.5, 3);
Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");
Wenn Sie keinen Feldnamen angeben, leitet der Compiler ihn möglicherweise vom Namen der entsprechenden Variablen in einem Tupelinitialisierungsausdruck ab, wie im folgenden Beispiel gezeigt:
var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");
Dieses Feature wird als Tupelprojektinitialisierer bezeichnet. Der Name einer Variablen wird in den folgenden Fällen nicht auf einen Tupelfeldnamen projiziert:
- Der Kandidatenname ist ein Membername eines Tupeltyps, z. B.
Item3,ToStringoderRest. - Beim Namen des Kandidaten handelt es sich um das Duplikat des expliziten oder impliziten Feldnamens eines anderen Tupels.
In den obigen Fällen geben Sie entweder explizit den Namen eines Felds an oder greifen über den Standardnamen auf ein Feld zu.
Die Standardnamen von Tupelfeldern sind Item1, , Item3Item2, usw. Den Standardnamen eines Felds können Sie immer verwenden. Dies gilt auch, wenn ein Feldname explizit angegeben oder abgeleitet wird (wie im folgenden Beispiel):
var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.
Bei der Tupelzuweisung und bei Vergleichen der Tupelgleichheit werden Feldnamen nicht berücksichtigt.
Zur Kompilierzeit ersetzt der Compiler die nicht dem Standard entsprechenden Felder durch die jeweiligen Standardnamen. Daher sind explizit angegebene oder abgeleitete Feldnamen zur Laufzeit nicht verfügbar.
Tipp
Aktivieren Sie die .NET-Codestilregel IDE0037, um eine Voreinstellung für abgeleitete oder explizite Tupelfeldnamen festzulegen.
Ab C# 12 können Sie mit einer using-Anweisung einen Alias für einen Tupeltyp angeben. Im folgenden Beispiel wird ein global using-Alias für einen Tupeltyp mit zwei Integerwerten für einen zulässigen Min-Wert und Max-Wert hinzugefügt:
global using BandPass = (int Min, int Max);
Nach dem Deklarieren des Alias können Sie den BandPass-Namen als Alias für diesen Tupeltyp verwenden:
BandPass bracket = (40, 100);
Console.WriteLine($"The bandpass filter is {bracket.Min} to {bracket.Max}");
Ein Alias führt keinen neuen Typ ein, sondern erstellt nur ein Synonym für einen vorhandenen Typ. Sie können ein Tupel dekonstruieren, das mit dem BandPass-Alias deklariert wurde. Dies funktioniert genauso wie mit dem zugrunde liegenden Tupeltyp:
(int a , int b) = bracket;
Console.WriteLine($"The bracket is {a} to {b}");
Wie bei der Tupelzuweisung oder -dekonstruktion müssen die Namen der Tupelmember nicht übereinstimmen, da die Typen übereinstimmen.
Ebenso kann ein zweiter Alias mit derselben Stelligkeit und denselben Membertypen austauschbar mit dem ursprünglichen Alias verwendet werden. Sie können einen zweiten Alias deklarieren:
using Range = (int Minimum, int Maximum);
Sie können einem Range-Tupel ein BandPass-Tupel zuweisen. Wie bei allen Tupelzuweisungen müssen die Feldnamen nicht übereinstimmen, sondern nur die Typen und die Stelligkeit.
Range r = bracket;
Console.WriteLine($"The range is {r.Minimum} to {r.Maximum}");
Ein Alias für einen Tupeltyp bietet mehr semantische Informationen, wenn Sie Tupel verwenden. Es wird kein neuer Typ eingeführt. Um die Typsicherheit sicherzustellen, sollten Sie stattdessen einen Positions-record deklarieren.
Tupelzuweisung und -dekonstruktion
C# unterstützt die Zuweisung zwischen Tupeltypen, die die beiden folgenden Bedingungen erfüllen:
- Beide Tupeltypen weisen dieselbe Anzahl von Elementen auf.
- Für jede Tupelposition entspricht der Typ des rechten Tupelelements dem Typ des entsprechenden linkshändigen Tupelelements oder wandelbar.
Weisen Sie Tupelelementwerte zu, indem Sie der Reihenfolge der Tupelelemente folgen. Der Zuordnungsprozess ignoriert die Namen von Tupelfeldern, wie im folgenden Beispiel gezeigt:
(int, double) t1 = (17, 3.14);
(double First, double Second) t2 = (0.0, 1.0);
t2 = t1;
Console.WriteLine($"{nameof(t2)}: {t2.First} and {t2.Second}");
// Output:
// t2: 17 and 3.14
(double A, double B) t3 = (2.0, 3.0);
t3 = t2;
Console.WriteLine($"{nameof(t3)}: {t3.A} and {t3.B}");
// Output:
// t3: 17 and 3.14
Verwenden Sie den Zuordnungsoperator = , um eine Tupelinstanz in separate Variablen zu deconieren . Dazu gibt es verschiedene Möglichkeiten:
Verwenden des Schlüsselworts
varaußerhalb der Klammern, um implizit typisierte Variablen zu deklarieren und die Typen vom Compiler ableiten zu lassen:var t = ("post office", 3.6); var (destination, distance) = t; Console.WriteLine($"Distance to {destination} is {distance} kilometers."); // Output: // Distance to post office is 3.6 kilometers.Explizites Deklarieren des Typs jeder Variablen in Klammern:
var t = ("post office", 3.6); (string destination, double distance) = t; Console.WriteLine($"Distance to {destination} is {distance} kilometers."); // Output: // Distance to post office is 3.6 kilometers.Deklarieren Sie einige Typen explizit und andere Typen implizit (mit
var) innerhalb der Klammern:var t = ("post office", 3.6); (var destination, double distance) = t; Console.WriteLine($"Distance to {destination} is {distance} kilometers."); // Output: // Distance to post office is 3.6 kilometers.Verwenden von vorhandenen Variablen:
var destination = string.Empty; var distance = 0.0; var t = ("post office", 3.6); (destination, distance) = t; Console.WriteLine($"Distance to {destination} is {distance} kilometers."); // Output: // Distance to post office is 3.6 kilometers.
Das Ziel eines Dekonstruktionsausdrucks kann sowohl vorhandene Variablen als auch Variablen enthalten, die in der Dekonstruktionsdeklaration deklariert wurden.
Sie können die Dekonstruktion auch mit einem Musterabgleich kombinieren, um die Eigenschaften von Feldern in einem Tupel zu untersuchen. Im folgenden Beispiel werden mehrere Integer durchlaufen und die durch 3 teilbaren ausgegeben. Es dekonstruiert das Tupelergebnis von Int32.DivRem und gleicht es mit einem Remainder von 0 ab:
for (int i = 4; i < 20; i++)
{
if (Math.DivRem(i, 3) is ( Quotient: var q, Remainder: 0 ))
{
Console.WriteLine($"{i} is divisible by 3, with quotient {q}");
}
}
Weitere Informationen zur Dekonstruktion von Tupeln und anderen Typen finden Sie unter Dekonstruieren von Tupeln und anderen Typen.
Tupelgleichheit
Tupeltypen unterstützen die Operatoren == und !=. Mit diesen Operatoren werden Member des linken Operanden gemäß der Reihenfolge der Tupelelemente mit den entsprechenden Membern des rechten Operanden verglichen.
(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right); // output: True
Console.WriteLine(left != right); // output: False
var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2); // output: True
Console.WriteLine(t1 != t2); // output: False
Wie im vorherigen Beispiel gezeigt, berücksichtigen die == Und != Vorgänge keine Tupelfeldnamen.
Zwei Tupel sind vergleichbar, wenn die beiden folgenden Bedingungen erfüllt sind:
- Beide Tupel weisen die gleiche Anzahl von Elementen auf.
t1 != t2wird beispielsweise nicht kompiliert, wennt1undt2über eine unterschiedliche Anzahl von Elementen verfügen. - Für jede Tupelposition sind die entsprechenden Elemente aus der linken und rechten Tupelopernden mit den
==Operatoren!=vergleichbar.(1, (2, 3)) == ((1, 2), 3)wird beispielsweise nicht kompiliert, da1nicht mit(1, 2)übereinstimmt.
Die == Tupel und != Operatoren vergleichen Tupel auf kurzschlussweise. Dies bedeutet, dass eine Operation sofort angehalten wird, wenn ein Paar mit ungleichen Elementen erkannt oder das Ende von Tupeln erreicht wird. Bevor ein Vergleich durchgeführt wird, werden aber alle Tupelelemente ausgewertet. Dies wird im folgenden Beispiel veranschaulicht:
Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));
int Display(int s)
{
Console.WriteLine(s);
return s;
}
// Output:
// 1
// 2
// 3
// 4
// False
Tupel als out-Parameter
Normalerweise gestalten Sie eine Methode, die out-Parameter enthält, in eine Methode um, die ein Tupel zurückgibt. Es gibt jedoch einige Fälle, in denen ein out Parameter ein Tupeltyp sein kann. Im folgenden Beispiel wird veranschaulicht, wie Sie Tupel als out-Parameter verwenden:
var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
[2] = (4, 10),
[4] = (10, 20),
[6] = (0, 23)
};
if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))
{
Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}
// Output:
// Found limits: min is 10, max is 20
Vergleich von Tupeln und System.Tuple
C#-Tupel verwenden System.ValueTuple Typen und unterscheiden sich von Tupeln, die Typen verwenden System.Tuple . Die wichtigsten Unterschiede sind:
- Bei
System.ValueTuple-Typen handelt es sich um Werttypen.System.Tuple-Typen sind Verweistypen. -
System.ValueTuple-Typen sind veränderlich.System.Tuple-Typen sind unveränderlich. - Datenmember von
System.ValueTuple-Typen sind Felder. Datenmember vonSystem.Tuple-Typen sind Eigenschaften.
C#-Sprachspezifikation
Weitere Informationen finden Sie unter