Udostępnij za pośrednictwem


Krotki i dekonstrukcja

Wskazówka

Dopiero zaczynasz programować oprogramowanie? Zacznij od samouczków Wprowadzenie . Napotkasz krotki, gdy musisz zwrócić wiele wartości z metody lub grupować wartości bez definiowania nazwanego typu.

Czy masz doświadczenie w pracy w innym języku? Krotki w języku C# są typami wartości, podobnymi do krotek w Pythonie lub Swifcie, ale oferują opcjonalne, nazwane elementy i pełną obsługę dekonstrukcji. Przejrzyj sekcje dekonstrukcji i równości pod kątem wzorców specyficznych dla języka C#.

Krotka grupuje wiele wartości w jedną, uproszczoną strukturę bez konieczności definiowania nazwanego typu. Krotki to typy wartości, które można zadeklarować w kodzie, zwracać z metod i rozpisać na poszczególne zmienne. Użyj krotki, gdy potrzebujesz tymczasowego, szybkiego grupowania powiązanych wartości. Na przykład w przypadku zwracania wielu wyników z metody lub przechowywania pary współrzędnych.

Poniższy przykład tworzy krotkę z nazwanymi elementami i odwołuje się do każdego elementu po nazwie.

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

Krotki działają dobrze w przypadku krótkotrwałych grup, w których definiowanie klasy, struktury lub rekordu spowodowałoby dodanie niepotrzebnej ceremonii. W przypadku długotrwałych pojęć lub typów domeny z zachowaniem preferuj rekordy, klasy lub struktury. Aby zapoznać się z porównaniem tego, kiedy używać każdego z nich, zobacz Wybieranie typu.

Deklarowanie i inicjowanie krotek

Zadeklaruj krotkę, wymieniając typy elementów w nawiasach. Opcjonalnie możesz nazwać każdy element, aby kod był bardziej czytelny:

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

Jeśli nie podajesz nazw, elementy używają nazw domyślnych Item1, Item2i tak dalej. Nazwane elementy umożliwiają samodzielne dokumentowanie kodu bez konieczności używania oddzielnej definicji typu.

Wywnioskowane nazwy elementów

Kompilator wywnioskuje nazwy elementów na podstawie nazw zmiennych lub właściwości, które używasz do inicjowania krotki. Ta funkcja pozwala uniknąć nadmiarowości, gdy nazwy są zgodne:

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

Wnioskowane nazwy zachowują zwięzłość kodu. Jeśli potrzebujesz innej nazwy elementu, określ ją jawnie.

Zwracanie wielu wartości z metody

Jednym z najpopularniejszych zastosowań krotek jest zwracanie wielu wartości z metody. Zamiast definiować klasę lub używać parametrów out, zwróć krotkę z nazwanymi elementami:

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

Nazwane krotki sprawiają, że zwracane wartości są czytelne zarówno w miejscu wywołania, jak i sygnaturze metody. Obiekt wywołujący może uzyskać dostęp do każdej wartości według nazwy bez konieczności zapamiętania kolejności pozycyjnej.

Dekonstrukcja krotek

Dekonstrukcja rozpakowuje elementy krotki na osobne zmienne w jednej instrukcji. Krotki dekonstrukcji można dekonstruować na kilka sposobów:

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

Dekonstrukcja jest szczególnie przydatna, gdy otrzymujesz krotkę z wywołania metody i musisz natychmiast pracować z jej poszczególnymi wartościami.

Krotki można dekonstrukować bezpośrednio w pętlach foreach, co sprawia, że iteracja nad kolekcjami grupowanych wartości jest zwięzła.

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

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

Jeśli nie potrzebujesz każdego elementu, użyj odrzucenia (_) zamiast każdej wartości, którą chcesz zignorować. Użyj oddzielnego _ elementu dla każdej odrzuconej pozycji:

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

Aby uzyskać więcej informacji na temat używania odrzucań w różnych kontekstach, zobacz Odrzucanie.

Równość krotek

Krotki można porównać przy użyciu operatorów == i !=. Te operatory porównują każdy element w kolejności, więc dwie krotki są równe, gdy wszystkie odpowiadające im elementy są równe:

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

Równość krotki używa operatora ==, który jest zdefiniowany dla każdego typu elementu, co oznacza, że porównanie działa poprawnie dla ciągów, liczb i innych typów, dla których zdefiniowano ==. Nazwy elementów nie mają wpływu na równość — mają znaczenie tylko wartości i pozycje.

Mutacja nieniszcząca z with

Wyrażenie with tworzy kopię krotki ze zmienionym co najmniej jednym elementem, pozostawiając oryginalny niezmieniony:

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

Ten wzorzec jest przydatny, gdy chcesz zmienić istniejącą krotkę bez modyfikowania oryginału. Wyrażenie with działa tak samo jak w krotkach, co w rekordach.

Krotki w słownikach i wyszukiwaniach

Krotki tworzą wygodne wartości słownika, gdy trzeba skojarzyć klucz z wieloma fragmentami danych:

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

Krotki działają również jako klucze słownika, zapewniając klucz złożony bez definiowania typu niestandardowego. Ponieważ krotki implementują równość strukturalną, wyszukania dopasowują się do połączonych wartości wszystkich elementów.

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

Ten wzorzec pozwala uniknąć konieczności użycia oddzielnej klasy dla prostych wyszukiwań wielokluczowych lub mapowań klucza do wielu wartości.

Krotki vs. typy anonimowe

Krotki są preferowanym wyborem, gdy potrzebujesz lekkiej, nieoznaczonej nazwy struktury danych. Typy anonimowe pozostają dostępne dla scenariuszy drzewa wyrażeń i kodu, który wymaga typów referencyjnych, ale krotki oferują lepszą wydajność, obsługę dekonstrukcji i bardziej elastyczną składnię. Aby uzyskać więcej informacji na temat typów anonimowych, zobacz Wybieranie między typami anonimowymi i krotkami.

Zobacz także