Рубріки: Теория

Зачем нужны кортежи: как работать с tuple в C#

Светлана Лазутина

Кортеж или tuple — это простой способ представить набор данных, который имеет несколько связанных или не связанных друг с другом значений.

По сути, это структуры данных, состоящих из нескольких частей.

«Скажи “кортеж” еще разок»

Содержание статьи:

1. Свойства кортежа

2. Создание кортежа

3. Доступ к элементам кортежа

4. Вложенные кортежи

5. Присваивание кортежей

6. Деконструкция кортежей

7. Отличия System.ValueTuple от SystemTuple

8. Подводя итог

Свойства кортежа

Представим ситуацию, когда вам нужно в одном объекте сохранить данные сотрудника, например, имя, фамилию, 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#

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023