Typy krotki (odwołanie w C#)
Funkcja krotki zapewnia zwięzłą składnię umożliwiającą grupowanie wielu elementów danych w lekkiej strukturze danych. W poniższym przykładzie pokazano, jak można zadeklarować zmienną krotki, zainicjować ją i uzyskać dostęp do jej składowych danych:
(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.
Jak pokazano w poprzednim przykładzie, aby zdefiniować typ krotki, należy określić typy wszystkich jego składowych danych i opcjonalnie nazwy pól. Nie można definiować metod w typie krotki, ale można użyć metod udostępnianych przez platformę .NET, jak pokazano w poniższym przykładzie:
(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.
Typy krotki obsługują operatory ==
równości i !=
. Aby uzyskać więcej informacji, zobacz sekcję Równość krotki.
Typy krotki są typami wartości; elementy krotki są polami publicznymi. To sprawia, że krotki modyfikowalne typy wartości.
Krotki można definiować z dowolną dużą liczbą elementów:
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
Przypadki użycia krotki
Jednym z najczęstszych przypadków użycia krotki jest typ zwracany przez metodę. Oznacza to, że zamiast definiowania out
parametrów metody można grupować wyniki metody w typie zwracanym krotki, jak pokazano w poniższym przykładzie:
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);
}
Jak pokazano w poprzednim przykładzie, możesz pracować z zwróconym wystąpieniem krotki bezpośrednio lub dekonstruktorować je w oddzielnych zmiennych.
Można również używać typów krotki zamiast typów anonimowych, na przykład w zapytaniach LINQ. Aby uzyskać więcej informacji, zobacz Wybieranie między typami anonimowych i krotki.
Zazwyczaj używa się krotki do grupowania luźno powiązanych elementów danych. W publicznych interfejsach API rozważ zdefiniowanie klasy lub typu struktury .
Nazwy pól krotki
Jawnie określasz nazwy pól krotek w wyrażeniu inicjalizacji krotki lub w definicji typu krotki, jak pokazano w poniższym przykładzie:
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}.");
Jeśli nie określisz nazwy pola, może ona zostać wywnioskowana z nazwy odpowiedniej zmiennej w wyrażeniu inicjalizacji krotki, jak pokazano w poniższym przykładzie:
var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");
Jest to nazywane inicjatorami projekcji krotki. Nazwa zmiennej nie jest rzutowana na nazwę pola krotki w następujących przypadkach:
- Nazwa kandydata to nazwa elementu członkowskiego typu krotki, na przykład
Item3
,ToString
lubRest
. - Nazwa kandydata jest duplikatem innej nazwy pola krotki, jawnej lub niejawnej.
W poprzednich przypadkach jawnie należy określić nazwę pola lub uzyskać dostęp do pola według domyślnej nazwy.
Domyślne nazwy pól krotki to Item1
, Item2
Item3
i tak dalej. Zawsze można użyć domyślnej nazwy pola, nawet jeśli nazwa pola jest określona jawnie lub wywnioskowana, jak pokazano w poniższym przykładzie:
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.
Przypisania krotki i porównania równości krotek nie uwzględniają nazw pól.
W czasie kompilacji kompilator zastępuje nazwy pól innych niż domyślne odpowiednimi nazwami domyślnymi. W związku z tym jawnie określone lub wnioskowane nazwy pól nie są dostępne w czasie wykonywania.
Napiwek
Włącz regułę stylu kodu platformy .NET IDE0037, aby ustawić preferencje dotyczące wywnioskowanych lub jawnych nazw pól krotki.
Począwszy od języka C# 12, można określić alias dla typu krotki z dyrektywąusing
. W poniższym przykładzie dodano global using
alias dla typu krotki z dwiema wartościami całkowitymi dozwolonymi Min
i Max
wartościami:
global using BandPass = (int Min, int Max);
Po zadeklarowaniu aliasu możesz użyć nazwy jako aliasu BandPass
dla tego typu krotki:
BandPass bracket = (40, 100);
Console.WriteLine($"The bandpass filter is {bracket.Min} to {bracket.Max}");
Alias nie wprowadza nowego typu, ale tworzy tylko synonim istniejącego typu. Możesz zdekonstruować krotkę zadeklarowaną za pomocą aliasu BandPass
tak samo jak w przypadku jego podstawowego typu krotki:
(int a , int b) = bracket;
Console.WriteLine($"The bracket is {a} to {b}");
Podobnie jak w przypadku przypisania krotki lub dekonstrukcji, nazwy składowych krotki nie muszą być zgodne; typy to robią.
Podobnie drugi alias z tym samymi typami arity i składowymi można używać zamiennie z oryginalnym aliasem. Możesz zadeklarować drugi alias:
using Range = (int Minimum, int Maximum);
Do krotki można przypisać Range
krotkę BandPass
. Podobnie jak w przypadku wszystkich przypisań krotek, nazwy pól muszą być niezgodne, tylko typy i arity.
Range r = bracket;
Console.WriteLine($"The range is {r.Minimum} to {r.Maximum}");
Alias dla typu krotki zapewnia więcej informacji semantycznych podczas korzystania z krotki. Nie wprowadza nowego typu. Aby zapewnić bezpieczeństwo typu, należy zadeklarować zamiast tego pozycyjny record
element.
Przypisanie krotki i dekonstrukcja
Język C# obsługuje przypisywanie między typami krotki, które spełniają oba następujące warunki:
- oba typy krotki mają taką samą liczbę elementów
- dla każdej pozycji krotki typ elementu krotki po prawej stronie jest taki sam jak lub niejawnie konwertowany na typ odpowiedniego elementu krotki po lewej stronie
Wartości elementów krotki są przypisywane zgodnie z kolejnością elementów krotki. Nazwy pól krotki są ignorowane i nie są przypisane, jak pokazano w poniższym przykładzie:
(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
Możesz również użyć operatora =
przypisania, aby zdekonstruować wystąpienie krotki w oddzielnych zmiennych. Można to zrobić na wiele sposobów:
Użyj słowa kluczowego
var
poza nawiasami, aby zadeklarować niejawnie typizowane zmienne i umożliwić kompilatorowi wnioskowanie ich typów: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.
Jawnie zadeklaruj typ każdej zmiennej wewnątrz nawiasów:
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.
Zadeklaruj niektóre typy jawnie i inne typy niejawnie (z
var
) wewnątrz nawiasów: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.
Użyj istniejących zmiennych:
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.
Miejsce docelowe wyrażenia dekonstrukcji może zawierać zarówno istniejące zmienne, jak i zmienne zadeklarowane w deklaracji dekonstrukcji.
Można również połączyć dekonstrukcję z dopasowaniem do wzorca, aby sprawdzić charakterystykę pól w krotki. Poniższy przykład wykonuje pętle przez kilka liczb całkowitych i drukuje te, które są podzielne przez 3. Dekonstrukuje wynik Int32.DivRem krotki i pasuje do wartości Remainder
0:
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}");
}
}
Aby uzyskać więcej informacji na temat dekonstrukcji krotki i innych typów, zobacz Dekonstrukcja krotki i inne typy.
Równość krotki
Typy krotki obsługują ==
operatory i !=
. Te operatory porównują elementy operandu po lewej stronie z odpowiednimi elementami operandu po prawej stronie po kolejności elementów krotki.
(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
Jak pokazano w poprzednim przykładzie, ==
operacje i !=
nie uwzględniają nazw pól krotki.
Dwie krotki są porównywalne, gdy oba następujące warunki są spełnione:
- Obie krotki mają taką samą liczbę elementów. Na przykład
t1 != t2
nie kompiluje, jeślit1
it2
ma różne liczby elementów. - Dla każdej pozycji krotki odpowiednie elementy z lewej i prawej strony operandy krotki są porównywalne z operatorami
==
i!=
. Na przykład nie kompiluje się,(1, (2, 3)) == ((1, 2), 3)
ponieważ1
nie jest porównywalna z parametrem(1, 2)
.
Operatory ==
i !=
porównują krotki w sposób zwarciowy. Oznacza to, że operacja zatrzymuje się, gdy tylko spełnia parę elementów niezrównowych lub osiąga końce krotki. Jednak przed porównaniem wszystkie elementy krotki są oceniane, jak pokazano w poniższym przykładzie:
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
Krotki jako parametry wychodzące
Zazwyczaj refaktoryzujesz metodę, która zawiera out
parametry w metodzie zwracającej krotkę. Istnieją jednak przypadki, w których out
parametr może być typu krotki. W poniższym przykładzie pokazano, jak pracować z krotkami jako out
parametrami:
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
Krotki a System.Tuple
Krotki języka C#, które są obsługiwane przez System.ValueTuple typy, różnią się od krotki reprezentowanych przez System.Tuple typy. Główne różnice są następujące:
System.ValueTuple
typy to typy wartości.System.Tuple
typy są typami referencyjnymi.System.ValueTuple
typy są modyfikowalne.System.Tuple
typy są niezmienne.- Składowe danych typów
System.ValueTuple
to pola. Składowe danych typówSystem.Tuple
są właściwościami.
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz: