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 = [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 = [-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, ToStringlub Rest.
  • 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, Item2Item3 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śli t1 i t2 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ów System.Tuple są właściwościami.

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz:

Zobacz też