Підрозділ 8.9
Кортежі
Пояснює кортежі в C#: доступ через Item, іменовані поля, декомпозицію, обмін значеннями, повернення й передачу кортежів у методах.
8.9. Кортежі
Класичне обмеження методу в C# — він може повертати лише одне значення. Щоб повернути два пов'язаних результати (наприклад, мінімум і максимум з масиву вимірювань), раніше доводилось або створювати спеціальний клас-обгортку, або використовувати out-параметри, або повертати масив. Усі ці підходи або зайво громіздкі, або не виразні. Кортежі (tuples), додані у C# 7.0, вирішують цю проблему елегантно: вони дозволяють групувати кілька значень в одну структуру без оголошення окремого класу.
System.Tuple vs ValueTuple: важлива різниця
У .NET існують два різних механізми кортежів, які легко переплутати.
System.Tuple<T1,T2,...> з'явився ще у .NET 4.0. Це звичайний клас — тобто reference type. Об'єкт розміщується у heap, а доступ до елементів — лише через Item1, Item2... Властивості readonly, синтаксис громіздкий: Tuple.Create("Іван", 45). Рівність — посилальна (як у всіх класів без перевизначення).
System.ValueTuple<T1,T2,...> з'явився у C# 7.0. Це структура — value type. Об'єкт розміщується на stack, що дає кращу продуктивність при частих виделеннях. Синтаксис короткий: ("Іван", 45). Поля мутабельні. Рівність — структурна (== порівнює значення). Саме цей варіант і має на увазі слово «кортеж» у сучасному C#.

Надалі, говорячи про кортежі, ми маємо на увазі виключно ValueTuple — сучасний та рекомендований варіант.
Синтаксис: оголошення та доступ
Кортеж визначається переліком значень у круглих дужках. Якщо не вказувати назви полів, доступ відбувається через Item1, Item2 і так далі:
var measurement = (36.6, 72); // (double, int)
Console.WriteLine(measurement.Item1); // 36.6
Console.WriteLine(measurement.Item2); // 72Рекомендований підхід — іменовані поля: вони роблять код значно зрозумілішим:
var measurement = (Temperature: 36.6, Pulse: 72);
Console.WriteLine(measurement.Temperature); // 36.6
Console.WriteLine(measurement.Pulse); // 72Іменовані поля — це виключно compile-time аліаси. Компілятор перетворює measurement.Temperature у measurement.Item1 під час компіляції. У скомпільованому IL-коді Temperature не існує — лише Item1. Це означає, що якщо ви передаєте кортеж у бібліотеку або зберігаєте у object, назви полів там будуть недоступні.
Тип кортежу можна вказати явно:
(double Temperature, int Pulse) measurement = (36.6, 72);Або визначити псевдонім типу через using (C# 12+):
using Measurement = (double Temperature, int Pulse);
Measurement m = (37.2, 88);Декомпозиція кортежу
Кортеж можна декомпозувати — розкласти на окремі змінні. Це особливо зручно при отриманні результату методу:
var (temperature, pulse) = GetVitals(patientId);
Console.WriteLine($"Температура: {temperature}, пульс: {pulse}");Якщо деякі елементи кортежу непотрібні, їх можна пропустити за допомогою discard (_):
var (temperature, _) = GetVitals(patientId); // пульс не потрібенDiscard — це не змінна, це явна вказівка компілятору, що значення нас не цікавить. Він не займає пам'яті і не створює змінної.
Основний синтаксис у клінічному контексті
Кортеж як результат методу
Найцінніше застосування кортежів — повернення кількох значень з методу. До C# 7 для цього використовували out-параметри або окремі класи. Тепер можна описати результат прямо в сигнатурі:
Іменований кортеж у сигнатурі методу виконує роль «легкого DTO»: він описує структуру результату прямо там, де метод визначений, без необхідності оголошувати окремий клас. Але на відміну від анонімного типу — може бути явно типізований і переданий між методами.
Кортеж як параметр методу
Кортеж можна передати і як параметр:
Рівність кортежів
ValueTuple підтримує структурну рівність через ==: два кортежі рівні, якщо рівні всі їхні елементи в тому самому порядку. Назви полів при порівнянні не враховуються.
var a = (Name: "Іван", Age: 45);
var b = (PatientName: "Іван", Years: 45); // інші назви — але однакові типи і значення
Console.WriteLine(a == b); // true — назви полів в IL відсутніКоли кортеж, а коли щось інше
| Потреба | Інструмент |
|---|---|
| Повернути 2–4 пов'язаних значення з методу | Кортеж (T1, T2, ...) |
| Тимчасова проекція тільки в межах методу | Анонімний тип (8.8) |
| Тип потрібний в кількох місцях, має поведінку | record (8.10) або клас |
| Потрібне успадкування або складний конструктор | Клас |
| Просто обміняти два значення | Кортеж (a, b) = (b, a) |
Головна перевага кортежу перед анонімним типом — можна вказати тип явно і передати між методами. Головна перевага перед класом — не треба оголошувати тип, якщо структура використовується лише локально.