Зачем нужны кортежи: как работать с tuple в C#
Кортеж или tuple
— это простой способ представить набор данных, который имеет несколько связанных или не связанных друг с другом значений.
По сути, это структуры данных, состоящих из нескольких частей.
Содержание статьи:
7. Отличия System.ValueTuple от SystemTuple
Свойства кортежа
Представим ситуацию, когда вам нужно в одном объекте сохранить данные сотрудника, например, имя, фамилию, ID работника, группу крови и т.д. С помощью кортежа можно создать общую структуру данных, куда будут входить все элементы. До появления кортежей было три основных варианта возврата нескольких значений из метода: использовался тип Class
или Struct
, или же данные возвращались через параметр out
В отличие от других структур данных, кортеж может хранить элементы любого типа, но только в ограниченном количестве. В кортежи можно добавлять до восьми элементов.
Если вы попытаетесь добавить девятый, то компилятор выдаст ошибку.
Другие структуры данных, такие как Array
или List
могут хранить бесконечное количество элементов, но они будут относиться только к определенному типу данных.
Например, tuple
может быть очень полезен при обработке регистрационной формы пользователя. Он позволяет обработать до восьми элементов и не придется создавать отдельную структуру для элементов разного типа.
Еще раз перечислим основные особенности кортежа:
- он позволяет объединять несколько элементов в один набор данных;
- с его помощью можно создавать, обрабатывать и получать доступ к набору данных;
- он может вернуть набор данных из метода без использования параметра
out
; - он может хранить повторяющиеся элементы;
- он может передавать несколько значений в метод с помощью отдельных параметров.
Общий синтаксис кортежа выглядит следующим образом:
Tuple <T1, T2, T3, T4, T5, T6, T7, TRest>
Создание кортежа
В C# существует два основных способа создания кортежей. Давайте познакомимся с ними поближе.
Конструктор класса tuple
Можно использовать класс Tuple <T>
для создания кортежа с определенной длиной и типом элементов, которые нужно сохранить.
В этом случае синтаксис кортежа будет выглядеть вот так:
// Конструктор для одного элемента Tuple <T1>(T1) // Конструктор для двух элементов Tuple <T1, T2>(T1, T2) . . . // Конструктор для восьми элементов Tuple <T1, T2, T3, T4, T5, T6, T7, TRest>(T1, T2, T3, T4, T5, T6, T7, TRest)
Теперь рассмотрим пример использования кортежа в программном коде:
using System; namespace tuples { class Tupling { static void Main(string[] args) { Tuple<string> MyStringTuple = new Tuple<string>("Pluralsight"); Tuple<string,int> MyCustomTuple = new Tuple<string,int>("Daniel",28); Tuple<int, int, int, int, int, int, int, Tuple<int>> MyMaxTuple = new Tuple<int, int, int, int, int, int, int, Tuple<int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int>(8)); Console.WriteLine($"The {nameof(MyStringTuple)} has the following elements: {MyStringTuple}"); Console.WriteLine($"The {nameof(MyCustomTuple)} has the following elements: {MyCustomTuple}"); Console.WriteLine($"The {nameof(MyMaxTuple)} has the following elements: {MyMaxTuple}"); Console.ReadKey(); ; } } }
Результат выполнения этих строк кода будет выглядеть так:
The MyStringTuple has the following elements: (Pluralsight) The MyCustomTuple has the following elements: (Daniel, 28) The MyMaxTuple has the following elements: (1, 2, 3, 4, 5, 6, 7, 8)
На примере видно, что код содержит три кортежа: первый — MyStringTuple
, второй — MyCustomTuple
и третий — MyMaxTuple
.
Главное, что нужно запомнить при создании кортежа с большим количеством элементов: в кортеже состоящем из восьми элементов, последний элемент всегда должен быть кортежем.
Метод create
Прошлый пример кода перегружен. Это произошло из-за того, что при использовании класса-конструктора нужно указать тип каждого элемента для кортежа. Получается, что чем длиннее кортеж, тем больше текста будет в вашем коде. Чтобы сделать код простым и читабельным, для класса tuple
был создан метод create
. В его основе методы, которые позволяют создавать экземпляр без указания типа каждого элемента.
Рассмотрим синтаксис кортежа, созданного через create
:
// Method for 1-tuple Create(T1) // Method for 2-tuple Create(T1, T2) . . . // Method for 8-tuple Create(T1, T2, T3, T4, T5, T6, T7, T8)
Уже на уровне синтаксиса все выглядит намного понятнее. Обратите внимание, что пример с кодом теперь стал легче, нет бесконечного повторения (int
) в программе:
using System; namespace tuples { class Tupling { static void Main(string[] args) { var MyStringTuple = Tuple.Create("Pluralsight"); var MyCustomTuple = Tuple.Create("Daniel",28); var MyMaxTuple = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8)); Console.WriteLine($"The {nameof(MyStringTuple)} has the following elements: {MyStringTuple}"); Console.WriteLine($"The {nameof(MyCustomTuple)} has the following elements: {MyCustomTuple}"); Console.WriteLine($"The {nameof(MyMaxTuple)} has the following elements: {MyMaxTuple}"); Console.ReadKey(); } } }
Результат выполнения программы будет выглядеть точно так же, как и в примере с конструктором. Метод создания кортежа не влияет на его работоспособность: первый и второй вариант выполняют код одинаково.
Доступ к элементам кортежа
Отобразить элементы кортежа можно с помощью свойства Item
. Например, Item1
, Item2
, Item3
— вплоть до Item7
. Свойство Item1
возвращает первый элемент, Item2
возвращает второй и так далее. Восьмой элемент возвращается с помощью свойства Rest
. Например:
// C# программа для доступа к кортежам using System; public class GFG { static public void Main() { // Создаем кортеж с одним элементом // Используем метод create var My_Tuple1 = Tuple.Create("GeeksforGeeks"); // Получаем доступ к элементам кортежа // Используем свойство Item Console.WriteLine("Element of My_Tuple1: " + My_Tuple1.Item1); Console.WriteLine(); // Создаем кортеж с 4 элементами var My_Tuple2 = Tuple.Create(12, 30, 40, 50); // Получаем доступ к элементам кортежа // Используем свойство Item Console.WriteLine("Element of My_Tuple2: " + My_Tuple2.Item1); Console.WriteLine("Element of My_Tuple2: " + My_Tuple2.Item2); Console.WriteLine("Element of My_Tuple2: " + My_Tuple2.Item3); Console.WriteLine("Element of My_Tuple2: " + My_Tuple2.Item4); Console.WriteLine(); // Создаем кортеж с 8 элементами var My_Tuple3 = Tuple.Create(13, "Geeks", 67, 89.90, 'g', 39939, "geek", 10); // Получаем доступ к элементам кортежа // Используем свойство Item // Печатаем 8 элементов кортежа // Используем свойство Rest Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item1); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item2); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item3); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item4); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item5); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item6); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Item7); Console.WriteLine("Element of My_Tuple3: " + My_Tuple3.Rest); }
Результат выполнения кода:
Element of My_Tuple1: GeeksforGeeks Element of My_Tuple2: 12 Element of My_Tuple2: 30 Element of My_Tuple2: 40 Element of My_Tuple2: 50 Element of My_Tuple3: 13 Element of My_Tuple3: Geeks Element of My_Tuple3: 67 Element of My_Tuple3: 89.9 Element of My_Tuple3: g Element of My_Tuple3: 39939 Element of My_Tuple3: geek Element of My_Tuple3: (10)
В результате мы получаем не одну строку с перечислением всех элементов кортежа, а перепись каждого отдельного элемента. Таким образом, с помощью Item
можно выводить в консоль не все элементы, а только необходимые.
Вложенные кортежи
Вложение используется, когда нужно добавить в кортеж больше восьми элементов. Для этого один кортеж вкладывают в другой. Лучше всего делать это в последнем элементе кортежа, но не обязательно.
Если вы выполняете вложение в конце, то можно получить доступ ко вложенному кортежу через свойство Rest
. Если вложенный кортеж находится в другом месте среди элементов, то придется использовать свойство Item
для доступа к нему.
Рассмотрим, как работают вложенные кортежи на примере:
using System; namespace tuples { class Tupling { static void Main(string[] args) { var MyMaxTupleNested = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21))); Console.WriteLine($"My original tuple: {MyMaxTupleNested}"); Console.WriteLine($"First element of the outest tuple: {MyMaxTupleNested.Item1}"); Console.WriteLine($"First level of nesting: {MyMaxTupleNested.Rest.Item1}"); Console.WriteLine($"Second level of nesting: {MyMaxTupleNested.Rest.Item1.Rest.Item1}"); Console.ReadKey(); } } }
Результат:
My original tuple: (1, 2, 3, 4, 5, 6, 7, (8, 9, 10, 11, 12, 13, 14, (15, 16, 17, 18, 19, 20, 21))) First element of the outest tuple: 1 First level of nesting: (8, 9, 10, 11, 12, 13, 14, (15, 16, 17, 18, 19, 20, 21)) Second level of nesting: (15, 16, 17, 18, 19, 20, 21)
Конечно, вложение кортежа в последний элемент выглядит лучше и чище, чем использование свойства Item
. В примере мы видим два вложенных кортежа, но в целом их количество неограничено.
Присваивание кортежей
В C# можно присвоить свойства одного кортежа другому. Фактически мы меняем значение кортежа. Для того чтобы провести такую операцию, кортежи должны:
- Иметь одинаковое количество элементов. Не получится присвоить свойства одного кортежа другому, если они выглядят вот так
Tuple.Create(12, 30, 40, 50) = Tuple.Create(7, 5);
. - Типы элементов в кортеже должны соответствовать друг другу. Нельзя заменить числа на элементы с буквами и наоборот.
Пример присвоения кортежа:
(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
Как мы видим в разделе output
, в первом случае кортеж t1
передал свои свойства кортежу t2
. Обратите внимание на то, что в случае присвоения кортежа первым в строке кода идет элемент, которому присваиваются свойства. А вот после знака =
стоит элемент, свойства которого переходят в левую часть уравнения.
Деконструкция кортежей
Кортежи можно деконструировать — разделить на части и присвоить эти части новым переменным. Таким образом можно получить доступ к отдельному полю или значению свойства. Деконструировать кортежи можно несколькими способами.
1Объявить тип каждой переменной в круглых скобках. Всегда используйте скобки, даже если все элементы в кортеже имеют один и тот же тип. Программа выдаст ошибку, если вы попытаетесь совершить деконструкцию без скобок:
var t = ("post office", 3.6); (string destination, double distance) = t; Console.WriteLine($"Distance to {destination} is {distance} kilometers."); // Результат: // Distance to post office is 3.6 kilometers.
2Использовать ключевое слово var
. С его помощью C# сам определит тип каждой переменной. Вы можете использовать ключевое слово var
двумя разными способами:
- вынести
var
за скобки; - поместить
var
в скобки с некоторыми или всеми переменными.
Например:
var(type1, name1, height1, age1, color1) = g.PetDetails("Dog", "Dollar", 124, 3, "White"); Console.WriteLine("Pet Details:"); Console.WriteLine("Type: " + type1); Console.WriteLine("Name: " + name1); Console.WriteLine("Height: " + height1); Console.WriteLine("Age: " + age1); Console.WriteLine("Color: " + color1); (var type2, var name2, var height2, var age2, var color2) = g.PetDetails("Cat", "Poo", 104, 1, "Black&White"); Console.WriteLine("\nPet Details:"); Console.WriteLine("Type: " + type2); Console.WriteLine("Name: " + name2); Console.WriteLine("Height: " + height2); Console.WriteLine("Age: " + age2); Console.WriteLine("Color: " + color2); Console.ReadLine();
3Деконструировать кортеж в уже существующую переменную. Например:
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."); // Результат: // Distance to post office is 3.6 kilometers.
4Можно игнорировать некоторые элементы кортежа. На C# 7.0 можно воспользоваться функцией discards
или сброса, чтобы игнорировать значения выбранных переменных. Чтобы пропустить ненужные элементы, используется знак нижнего подчеркивания _
. Синтаксис такого выражения выглядит так:
(var1, _, var3, _, var5) = method_name(var1_values, var2_values, var3_value, var4_values, var5_value);
Пример кода с discards
:
using System; public class ExampleDiscard { public static void Main() { var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010); Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}"); } private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2) { int population1 = 0, population2 = 0; double area = 0; if (name == "New York City") { area = 468.48; if (year1 == 1960) { population1 = 7781984; } if (year2 == 2010) { population2 = 8175133; } return (name, area, year1, population1, year2, population2); } return ("", 0, 0, 0, 0, 0); } } // Результат: // Population change, 1960 to 2010: 393,149
В этом примере метод QueryCityDataForYears
возвращает кортеж из шести элементов с названием города, площадью, годом, населением города за этот год, второй год и численностью населения этот год. Нам нужно узнать, как изменилась численность населения за эти два года. Получается, что из данных кортежа нас интересуют только два значения. Остальные элементы мы просто отбрасываем и получаем выражение var (_, _, _, pop1, _, pop2)
.
Отличия System.ValueTuple от SystemTuple
ValueTuple
— это структура, которая отображает кортеж так же, как класс System.Tuple
.
Различия между элементами System.ValueTuple и SystemTuple
х | System.ValueTuple |
System.Tuple |
Тип | Значение (структура) | Ссылочный тип (класс) |
Свойства структуры | Изменяемая | Неизменная |
Предоставление элементов | С помощью полей | С помощью свойств |
Подводя итог
Кортежи удобно использовать для работы с элементами данных разного типа данных и для обеспечения простого доступа к этим данным. Кортежи помогут, когда необходимо:
- вернуть несколько значений из метода без использования параметров
ref
илиout
; - передать несколько значений методу через один параметр;
- сохранить запись в базе данных или другие значения без создания отдельного класса.
Главный минус кортежей — они поддерживают не больше восьми элементов. Но этот недостаток можно обойти с помощью вложенности.
Видеоурок по кортежам в C#
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: