OOP Course
Сьогодні

Підрозділ 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#.

Кортежі в C#: System.Tuple (старий) vs ValueTuple (новий)

Надалі, говорячи про кортежі, ми маємо на увазі виключно 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)

Головна перевага кортежу перед анонімним типом — можна вказати тип явно і передати між методами. Головна перевага перед класом — не треба оголошувати тип, якщо структура використовується лише локально.

Розроблено Tomka Yurii · © 2026 ·