Кортежи появились в C# начиная с версии 7.0 с целью обеспечения работы с наборами значений. Основное предназначение кортежей - обобщение нескольких элементов в структуру с упрощенным синтаксисом. Для использования кортежей необходим тип System.ValueTuple. Для использования кортежей в более ранних версиях .NET Framework в проект необходимо добавить NuGet пакет SystemValueTuple.

Основные свойства кортежей:

  • кортежи определяются с неограниченным количеством элементов.

  • Кортежи являются типом значений.

  • Кортежи поддерживают операторы = и !=

  • Значения кортежей являются общедоступными полями.

var tuple1 = (10, 23.4); //Определен кортеж tuple1 с двумя значениями 10 и 23.4

Обращение к значениям кортежа происходит через поля с названиями типа Item[номер], где номер - порядковый номер значения в кортеже. Рассмотрим на примере программы:

static void Main(string[] args)
{
  int tuple1 = (11, 33);						//Определение кортежа с целочисленными значениями
  Console.WriteLine(tuple1.Item1);	//Вывод на консоль 11
  tuple1.Item2 *= 2;								//Умножение второго значения на 2
}

Возможно определение типа для каждого из значений. Для этого перед именем кортежа в скобках указываются типы для каждого значения.

(string, int) tuple = ("Coin", 25);

Для каждого значения кортежа предусмотрена возможность индивидуального именования. Заданное имя элемента называется именем кандидата. Оно является дубликатором другого явного или неявного имени поля кортежа.

var tuple1 = (purchase:5, sale:10);
Console.WriteLine(tuple1.purchase);

Для определения кортежей можно применять переменные, с которыми в дальнейшем можно производить операции.

var (purchase, sale) = (5, 10);
Console.WriteLine(Purchase);

Применение кортежей

Кортежи могут передаваться в метод в качестве параметров и служить возвращаемым результатом. В частности, очень удобно возвращать кортеж в качестве результата в том случае, если метод возвращает только одно значение.

static void Main(string[] args)
{
    var tuple1 = Method();					//Присваиваем кортежу tuple1 возвращаемое значение
    Console.WriteLine(tuple1.Item1);
    Console.WriteLine(tuple1.Item2);     
}
private static (int, int) Method()	//Метод, возвращающий кортеж
{
    var res = (5, 10);
    return res;
}

В следующем примере кортеж передается в метод в качестве параметра.

static void Main(string[] args)
{
  var (purchase, sale) = Method ((5, 10), 3);
  Console.WriteLine($"Purchase: {purchase} Sale: {sale}");
}

private static (int purchase, int sale) Method((int p, int s)tuple, int n)
{
  var res = (purchase: tuple.p, sale: tuple.s + n);
  return result;
}

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

В кортежах предусмотрена возможность присваивания. Для этого оба кортежа должны иметь одинаковое количество элементов, типы соответствующих значений должны совпадать или иметь возможность неявного приведения друг к другу. Значения присваиваются в порядке их расположения. При присвоении имена полей не учитываются.

(int, int) tuple1 = (22, 12);
(double A, double B) tuple2 = (2.15, 3,02);
tuple2 = tuple1;

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

Деконструкцией кортежей называется операция присваивания экземпляра кортежа в отдельные переменные.

var tuple = ("Alex", 36);
(string name, int age) = tuple;
//или
string name;
int age;
(name, age) = tuple;

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

var tuple1 = (23, 36);
var tuple2 = (17, 31);
Console.WriteLine(tuple1 == tuple2);
Console.WriteLine(tuple1 != tuple2);

Имена полей кортежей при сравнении не учитываются.

Условия возможности сравнения кортежей:

  • Оба кортежа содержат одинаковое количество элементов.

  • Элементы должны соответствовать возможности сравнения.

Кортежи в качестве параметров вывода

При выполнении рефакторинга метода, имеющего параметры out они могут иметь тип кортежа.

var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))
{
    Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}

Различия между типами кортежей System.ValueTuple и SystemTuple

Основные различия заключаются в следующем:

  • Типы ValueTuple являются типами значений - типы Tuple - ссылочные типы.

  • Типы ValueTuple являются изменяемыми типами - типы Tuple - неизменяемые.

  • Значениями типов ValueTuple являются поля. Значениями типов Tuple являются свойства.

Список использованных источников

Комментарии (7)


  1. Matisumi
    16.08.2021 10:32
    +9

    Это здорово, конечно, но зачем это здесь? Что нового, того что нет в документации, можно узнать из этой публикации?


    1. derikn_mike
      16.08.2021 12:04
      -1

      например я узнал что это не синтаксический сахар , очень был уверен что это он , а тут оказывается разные типы


      1. WhiteBlackGoose
        16.08.2021 13:22
        +5

        Во-первых это, конечно, есть в документации. Во-вторых, синтаксис валуетуплов это сахар


      1. syusifov
        16.08.2021 23:27

        стенка ждет


  1. syusifov
    16.08.2021 12:11
    -1

    А это ты не видел - Tuple Класс (System) | Microsoft Docs


  1. anonymous
    00.00.0000 00:00


  1. BkmzSpb
    16.08.2021 16:29
    +4

    Неделя началась с постов про C#, что ж.

    кортежи определяются с неограниченным количеством элементов.

    Здесь было бы уместно сказать, что кортеж это просто сахар над System.ValueTuple<...>. И что у этого есть проблема, такая же как у System.Action и System.Func -- конечное число комбинаций.
    Так если ваш кортеж/тапл содержит до 7 элементов, под это все выписаны отдельные дженерики (включая кортеж размера 0 и 1, хотя создать их с помощь сахара нельзя).
    А дальше начинается фигня, потому что ValueTuple`8 содержит 7 дженерик полей, а 8-ое зарезервировано под такой же кортеж. Поэтому кортеж из 8 элементов это тип, который содержит 7 первых элементов и 8-ой, обернутый в 1-кортеж. А если у вас 10 элементов, то это все равно ValueTuple`8,но последний элемент это 3-кортеж, и так далее.
    См примеры на SharpLab и имплементацию на GitHub.

    А еще ваш кортеж реализует ITuple, такой же интерфейс реализуют и обычные System.Tuple<>. Поэтому кортежи можно проверять на ITuple, который имеет длину и индексатор типа object. Эта фича используется, кстати, при pattern-matching.
    Если у вас object o is (int a, int b), то проверка идет не на ValueTuple а на интерфейс, а значит и Tuple тоже подойдет. Конрад Кокоса, кажется, пригорал по этому поводу в одном из твитов, говорил что вместо такого паттерна проще чекать по GetType и потом явно приводить тип -- получалось быстрее, чем доставать из интерфейса objects и потом каждый распаковывать в нужный тип. Ссылки на твит не найду, но вот ссылка на пример на ShapLab, где видна разница. Не силен в asm, но кажется даже JIT-код содержит вызовы всяких Unbox, что вряд ли показатель быстрого кода.
    Зато если у вас свой тип реализует ITuple, он должен честно паттерн-матчистя и деконструироваться (но я не првоерял).

    Ах да, в топике про деконструкцию хорошо бы было упомянуть и деконструкцию произвольных типов по сигнатуре public void Deconstruct(out T1 p1, out T2 p2, /* .. */). Можно как инстанс метод, можно как экстеншн метод навесить на чужой тип. После этого работает (T1 p1, T2 p2) = myTypeInstance, ну и весь синтаксический сахар паттернов. Хорошо сочетается с foreach.