Типы кортежей (справочник по C#)

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

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

Как показано в предыдущем примере, для определения типа кортежа необходимо указать типы всех его элементов данных и, при необходимости, имена полей. Невозможно определить методы в типе кортежа, но можно использовать методы, предоставляемые .NET, как показано в следующем примере:

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

Типы кортежей поддерживают операторы== равенства и !=. Дополнительные сведения см. в разделе Равенство кортежей.

Типы кортежей являются типами значений, а элементы кортежа — общедоступными полями. Поэтому кортежи представляют собой изменяемые типы значений.

Можно определить кортежи со сколь угодно большим числом элементов.

var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26);  // output: 26

Варианты использования кортежей

Чаще всего кортежи используются как возвращаемый методом тип. То есть вместо определения параметров метода out можно сгруппировать результаты метода в возвращаемый тип кортежа, как показано в следующем примере.

int[] xs = [4, 7, 9];
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9

int[] ys = [-9, 0, 67, 100];
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100

(int min, int max) FindMinMax(int[] input)
{
    if (input is null || input.Length == 0)
    {
        throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
    }

    // Initialize min to MaxValue so every value in the input
    // is less than this initial value.
    var min = int.MaxValue;
    // Initialize max to MinValue so every value in the input
    // is greater than this initial value.
    var max = int.MinValue;
    foreach (var i in input)
    {
        if (i < min)
        {
            min = i;
        }
        if (i > max)
        {
            max = i;
        }
    }
    return (min, max);
}

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

Типы кортежей можно также использовать вместо анонимных типов, например в запросах LINQ. Дополнительные сведения см. в статье Выбор между анонимными типами и кортежами.

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

Имена полей кортежей

Вы явно указываете имена полей кортежей в выражении инициализации кортежа или в определении типа кортежа, как показано в следующем примере:

var t = (Sum: 4.5, Count: 3);
Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");

(double Sum, int Count) d = (4.5, 3);
Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");

Если имя поля не указано, оно может быть выведено из имени соответствующей переменной в выражении инициализации кортежа, как показано в следующем примере:

var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");

Это называется инициализаторами проекции кортежей. Имя переменной не проецируется на имя поля кортежа в следующих случаях:

  • Имя кандидата — это имя элемента типа кортежа, например Item3, ToStringили Rest.
  • Имя кандидата является дубликатом другого имени поля кортежа, явного или неявного.

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

По умолчанию поля кортежа имеют имена Item1, Item2, Item3 и т. д. Всегда можно использовать имя поля по умолчанию, даже если имя поля указано явно или является выводимым, как показано в следующем примере.

var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.

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

Во время компиляции компилятор заменяет имена полей не по умолчанию соответствующими именами по умолчанию. В результате явно указанные или выводимые имена полей будут недоступны во время выполнения.

Совет

Включите правило стиля кода .NET IDE0037 , чтобы задать предпочтения для выводимых или явных имен полей кортежей.

Начиная с C# 12, можно указать псевдоним для типа кортежа с директивойusing. В следующем примере добавляется global using псевдоним для типа кортежа с двумя целыми значениями допустимого Min и Max значения:

global using BandPass = (int Min, int Max);

После объявления псевдонима можно использовать BandPass имя в качестве псевдонима для этого типа кортежа:

BandPass bracket = (40, 100);
Console.WriteLine($"The bandpass filter is {bracket.Min} to {bracket.Max}");

Псевдоним не вводит новый тип, но создает синоним только для существующего типа. Вы можете деконструировать кортеж, объявленный псевдонимом BandPass так же, как и с его базовым типом кортежа:

(int a , int b) = bracket;
Console.WriteLine($"The bracket is {a} to {b}");

Как и при назначении кортежа или деконструкции, имена элементов кортежа не должны соответствовать; Типы выполняются.

Аналогичным образом можно использовать второй псевдоним с одинаковыми типами arity и членов взаимозаменяемо с исходным псевдонимом. Можно объявить второй псевдоним:

using Range = (int Minimum, int Maximum);

Кортеж можно назначить RangeBandPass кортеже. Как и во всех назначениях кортежей, имена полей не должны совпадать, только типы и arity.

Range r = bracket;
Console.WriteLine($"The range is {r.Minimum} to {r.Maximum}");

Псевдоним для типа кортежа предоставляет более семантические сведения при использовании кортежей. Он не вводит новый тип. Чтобы обеспечить безопасность типов, вместо этого следует объявить позиционный record элемент.

Присваивание и деконструкция кортежей

В C# поддерживается присваивание между типами кортежей, которые соответствуют обоим следующим условиям:

  • оба типа кортежей должны содержать одинаковое количество элементов;
  • для каждой позиции кортежа тип правого элемента кортежа аналогичен типу соответствующего левого элемента кортежа или может быть неявно преобразован в этот тип.

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

(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

Оператор присваивания = можно также использовать для деконструкции экземпляра кортежа в отдельные переменные. Это можно сделать различными способами:

  • Вы можете использовать ключевое слово var за пределами круглых скобок, чтобы объявить неявно типизированные переменные и позволить компилятору вывести их типы.

    var t = ("post office", 3.6);
    var (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Вы можете явно объявить тип каждой переменной в скобках.

    var t = ("post office", 3.6);
    (string destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Объявите некоторые типы явно и другие типы неявно (с var) внутри скобки:

    var t = ("post office", 3.6);
    (var destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Вы можете использовать существующие переменные.

    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.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    

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

Вы также можете объединить деконструкцию с сопоставлением шаблонов, чтобы проверить характеристики полей в кортеже. В следующем примере выполняется цикл по нескольким целым числам и выводится число, которое делится на 3. Он деконструирует результат Int32.DivRem кортежа и соответствует Remainder 0:

for (int i = 4; i < 20;  i++)
{
    if (Math.DivRem(i, 3) is ( Quotient: var q, Remainder: 0 ))
    {
        Console.WriteLine($"{i} is divisible by 3, with quotient {q}");
    }
}

Подробнее о деконструкции кортежей с помощью и других типов см. в статье Деконструкция кортежей и других типов.

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

Типы кортежей == поддерживают операторы и != операторы. Эти операторы сравнивают элементы левого операнда с соответствующими элементами правого операнда в соответствии с порядком расположения элементов кортежа.

(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right);  // output: True
Console.WriteLine(left != right);  // output: False

var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2);  // output: True
Console.WriteLine(t1 != t2);  // output: False

Как показано в предыдущем примере, в операциях == и != не учитываются имена полей кортежей.

Два кортежа сравнимы, если выполнены оба следующих условия:

  • оба кортежа содержат одинаковое количество элементов. Например, t1 != t2 не компилируется, если t1 и t2 имеют разное количество элементов.
  • Для каждой позиции кортежа соответствующие элементы из левого и правого операндов кортежа сравниваются с помощью операторов == и !=. Например, не компилируется, (1, (2, 3)) == ((1, 2), 3) так как 1 не сравнивается с (1, 2).

Операторы == и != сравнивают кортежи с сокращенной обработкой. Это значит, что операция останавливается, как только она соответствует паре неравных элементов или достигает конца кортежей. Однако перед любым сравнением все элементы кортежа вычисляются, как показано в следующем примере.

Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));

int Display(int s)
{
    Console.WriteLine(s);
    return s;
}
// Output:
// 1
// 2
// 3
// 4
// False

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

Как правило, вы выполняете рефакторинг метода, имеющего параметры out, в метод, возвращающий кортеж. Однако бывают случаи, когда параметр out может иметь тип кортежа. В следующем примере показано, как работать с кортежами в виде параметров 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}");
}
// Output:
// Found limits: min is 10, max is 20

Кортежи и System.Tuple

Кортежи C# с типами System.ValueTuple, отличаются от кортежей, представленных типами System.Tuple. Основные различия заключаются в следующем.

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

Спецификация языка C#

Дополнительные сведения см. в разделе:

См. также