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

Кортежи , доступные в C# 7.0 и более поздних версиях, предоставляют краткий синтаксис для группирования нескольких элементов данных в упрощенную структуру данных.Available in C# 7.0 and later, the tuples feature provides concise syntax to group multiple data elements in a lightweight data structure. В следующем примере показано, как можно объявить переменную кортежа, инициализировать ее и получить доступ к ее элементам данных.The following example shows how you can declare a tuple variable, initialize it, and access its data members:

(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.

Как показано в предыдущем примере, для определения типа кортежа необходимо указать типы всех его элементов данных и, при необходимости, имена полей.As the preceding example shows, to define a tuple type, you specify types of all its data members and, optionally, the field names. В типе кортежа невозможно определить методы, но можно использовать методы, предоставляемые .NET, как показано в следующем примере.You cannot define methods in a tuple type, but you can use the methods provided by .NET, as the following example shows:

(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.

Начиная с C# 7.3, типы кортежей поддерживают операторы равенства == и !=.Beginning with C# 7.3, tuple types support equality operators == and !=. Дополнительные сведения см. в разделе Равенство кортежей.For more information, see the Tuple equality section.

Типы кортежей являются типами значений, а элементы кортежа — общедоступными полями.Tuple types are value types; tuple elements are public fields. Поэтому кортежи представляют собой изменяемые типы значений.That makes tuples mutable value types.

Примечание

Для кортежей требуется тип System.ValueTuple и связанные универсальные типы (например, System.ValueTuple<T1,T2>), доступные в .NET Core и .NET Framework 4.7 и более поздних версий.The tuples feature requires the System.ValueTuple type and related generic types (for example, System.ValueTuple<T1,T2>), which are available in .NET Core and .NET Framework 4.7 and later. Чтобы использовать кортежи в проекте, предназначенном для .NET Framework 4.6.2 или более ранней версии, добавьте в проект пакет NuGet System.ValueTuple.To use tuples in a project that targets .NET Framework 4.6.2 or earlier, add the NuGet package System.ValueTuple to the project.

Можно определить кортежи со сколь угодно большим числом элементов.You can define tuples with an arbitrary large number of elements:

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

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

Чаще всего кортежи используются как возвращаемый методом тип.One of the most common use cases of tuples is as a method return type. То есть вместо определения параметров метода out можно сгруппировать результаты метода в возвращаемый тип кортежа, как показано в следующем примере.That is, instead of defining out method parameters, you can group method results in a tuple return type, as the following example shows:

var xs = new[] { 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

var ys = new[] { -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.");
    }

    var min = int.MaxValue;
    var max = int.MinValue;
    foreach (var i in input)
    {
        if (i < min)
        {
            min = i;
        }
        if (i > max)
        {
            max = i;
        }
    }
    return (min, max);
}

Как показано в предыдущем примере, с возвращаемым экземпляром кортежа можно работать напрямую или деконструировать его в отдельные переменные.As the preceding example shows, you can work with the returned tuple instance directly or deconstruct it in separate variables.

Типы кортежей можно также использовать вместо анонимных типов, например в запросах LINQ.You can also use tuple types instead of anonymous types; for example, in LINQ queries. Дополнительные сведения см. в статье Выбор между анонимными типами и кортежами.For more information, see Choosing between anonymous and tuple types.

Как правило, кортежи используются для группирования слабо связанных элементов данных.Typically, you use tuples to group loosely related data elements. Это целесообразно в закрытых и внутренних служебных методах.That is usually useful within private and internal utility methods. При работе с общедоступным API рассмотрите возможность определения типа класса или структуры.In the case of public API, consider defining a class or a structure type.

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

Имена полей кортежей указываются явным образом либо в выражении инициализации кортежа, либо в определении типа кортежа, как показано в следующем примере.You can explicitly specify the names of tuple fields either in a tuple initialization expression or in the definition of a tuple type, as the following example shows:

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}.");

Начиная с C# 7.1, если имя поля не указано, оно может быть выведено из имени соответствующей переменной в выражении инициализации кортежа, как показано в следующем примере.Beginning with C# 7.1, if you don't specify a field name, it may be inferred from the name of the corresponding variable in a tuple initialization expression, as the following example shows:

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

Это называется инициализаторами проекций кортежа.That's known as tuple projection initializers. Имя переменной не проецируется на имя поля кортежа в следующих случаях:The name of a variable isn't projected onto a tuple field name in the following cases:

  • Имя кандидата — это имя элемента типа кортежа, например Item3, ToStringили Rest.The candidate name is a member name of a tuple type, for example, Item3, ToString, or Rest.
  • Имя кандидата является дубликатом другого имени поля кортежа, явного или неявного.The candidate name is a duplicate of another tuple field name, either explicit or implicit.

В этих случаях необходимо либо явно указать имя поля, либо получить доступ к полю по имени по умолчанию.In those cases you either explicitly specify the name of a field or access a field by its default name.

По умолчанию поля кортежа имеют имена Item1, Item2, Item3 и т. д.The default names of tuple fields are Item1, Item2, Item3 and so on. Всегда можно использовать имя поля по умолчанию, даже если имя поля указано явно или является выводимым, как показано в следующем примере.You can always use the default name of a field, even when a field name is specified explicitly or inferred, as the following example shows:

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.

Имена полей не учитываются при присваивании кортежа и сравнении кортежей на равенство.Tuple assignment and tuple equality comparisons don't take field names into account.

Во время компиляции компилятор заменяет имена полей не по умолчанию соответствующими именами по умолчанию.At compile time, the compiler replaces non-default field names with the corresponding default names. В результате явно указанные или выводимые имена полей будут недоступны во время выполнения.As a result, explicitly specified or inferred field names aren't available at run time.

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

В C# поддерживается присваивание между типами кортежей, которые соответствуют обоим следующим условиям:C# supports assignment between tuple types that satisfy both of the following conditions:

  • оба типа кортежей должны содержать одинаковое количество элементов;both tuple types have the same number of elements
  • для каждой позиции кортежа тип правого элемента кортежа аналогичен типу соответствующего левого элемента кортежа или может быть неявно преобразован в этот тип.for each tuple position, the type of the right-hand tuple element is the same as or implicitly convertible to the type of the corresponding left-hand tuple element

Значения элементов кортежа присваиваются в порядке расположения элементов кортежа.Tuple element values are assigned following the order of tuple elements. Имена полей кортежа не учитываются и не присваиваются, как показано в следующем примере.The names of tuple fields are ignored and not assigned, as the following example shows:

(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

Оператор присваивания = можно также использовать для деконструкции экземпляра кортежа в отдельные переменные.You can also use the assignment operator = to deconstruct a tuple instance in separate variables. Существует три способа деконструкции кортежа.You can do that in one of the following ways:

  • Вы можете явно объявить тип каждой переменной в скобках.Explicitly declare the type of each variable inside parentheses:

    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 за пределами круглых скобок, чтобы объявить неявно типизированные переменные и позволить компилятору вывести их типы.Use the var keyword outside the parentheses to declare implicitly typed variables and let the compiler infer their types:

    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.
    
  • Вы можете использовать существующие переменные.Use existing variables:

    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.
    

Подробнее о деконструкции кортежей с помощью и других типов см. в статье Деконструкция кортежей и других типов.For more information about deconstruction of tuples and other types, see Deconstructing tuples and other types.

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

Начиная с C# 7.3, типы кортежей поддерживают операторы == и !=.Beginning with C# 7.3, tuple types support the == and != operators. Эти операторы сравнивают элементы левого операнда с соответствующими элементами правого операнда в соответствии с порядком расположения элементов кортежа.These operators compare members of the left-hand operand with the corresponding members of the right-hand operand following the order of tuple elements.

(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

Как показано в предыдущем примере, в операциях == и != не учитываются имена полей кортежей.As the preceding example shows, the == and != operations don't take into account tuple field names.

Два кортежа сравнимы, если выполнены оба следующих условия:Two tuples are comparable when both of the following conditions are satisfied:

  • оба кортежа содержат одинаковое количество элементов.Both tuples have the same number of elements. Например, t1 != t2 не компилируется, если t1 и t2 имеют разное количество элементов.For example, t1 != t2 doesn't compile if t1 and t2 have different numbers of elements.
  • Для каждой позиции кортежа соответствующие элементы из левого и правого операндов кортежа сравниваются с помощью операторов == и !=.For each tuple position, the corresponding elements from the left-hand and right-hand tuple operands are comparable with the == and != operators. Например, (1, (2, 3)) == ((1, 2), 3) не компилируется, поскольку 1 не сравнивается с помощью (1, 2).For example, (1, (2, 3)) == ((1, 2), 3) doesn't compile because 1 is not comparable with (1, 2).

Операторы == и != сравнивают кортежи с сокращенной обработкой.The == and != operators compare tuples in short-circuiting way. Это значит, что операция останавливается, как только она соответствует паре неравных элементов или достигает конца кортежей.That is, an operation stops as soon as it meets a pair of non equal elements or reaches the ends of tuples. Однако перед любым сравнением все элементы кортежа вычисляются, как показано в следующем примере.However, before any comparison, all tuple elements are evaluated, as the following example shows:

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

Кортежи как параметры выводаTuples as out parameters

Как правило, вы выполняете рефакторинг метода, имеющего параметры out, в метод, возвращающий кортеж.Typically, you refactor a method that has out parameters into a method that returns a tuple. Однако бывают случаи, когда параметр out может иметь тип кортежа.However, there are cases in which an out parameter can be of a tuple type. В следующем примере показано, как работать с кортежами в виде параметров out.The following example shows how to work with tuples as out parameters:

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.TupleTuples vs System.Tuple

Кортежи C# с типами System.ValueTuple, отличаются от кортежей, представленных типами System.Tuple.C# tuples, which are backed by System.ValueTuple types, are different from tuples that are represented by System.Tuple types. Основные различия заключаются в следующем.The main differences are as follows:

  • Типы ValueTuple являются типами значений.ValueTuple types are value types. Типы Tuple являются ссылочными типами.Tuple types are reference types.
  • Типы ValueTuple являются изменяемыми.ValueTuple types are mutable. Типы Tuple являются неизменяемыми.Tuple types are immutable.
  • Элементами данных типов ValueTuple являются поля.Data members of ValueTuple types are fields. Элементами данных типов Tuple являются свойства.Data members of Tuple types are properties.

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

Дополнительные сведения см. в следующих примечаниях к предлагаемой функции.For more information, see the following feature proposal notes:

См. такжеSee also