(string, int, string) values = ("a", 5, "c");
var values = ("a", 5, "c");
По умолчанию компилятор назначает каждому свойству имя ItemX
, где X
представляет позицию свойства в кортеже, начиная с 1. В предыдущем примере свойства именуются как Item1
, Item2
и Item3
. Доступ к ним осуществляется следующим образом:
Console.WriteLine($"First item: {values.Item1}"); // Первый элемент
Console.WriteLine($"Second item: {values.Item2}"); // Второй элемент
Console.WriteLine($"Third item: {values.Item3}"); // Третий элемент
Кроме того, к каждому свойству кортежа справа или слева можно добавить специфическое имя. Хотя назначение имен в обеих частях оператора не приводит к ошибке на этапе компиляции, имена в правой части игнорируются, а использоваться будут имена в левой части. Показанные ниже две строки кода демонстрируют установку имен в левой и правой частях оператора, давая тот же самый результат:
(string FirstLetter, int TheNumber, string SecondLetter)
valuesWithNames = ("a", 5, "c");
var valuesWithNames2 = (FirstLetter: "a", TheNumber: 5, SecondLetter: "c");
Теперь доступ к свойствам кортежа возможен с применением имен полей, а также системы обозначений ItemX
:
Console.WriteLine($"First item: {valuesWithNames.FirstLetter}");
Console.WriteLine($"Second item: {valuesWithNames.TheNumber}");
Console.WriteLine($"Third item: {valuesWithNames.SecondLetter}");
// Система обозначений ItemX по-прежнему работает!
Console.WriteLine($"First item: {valuesWithNames.Item1}");
Console.WriteLine($"Second item: {valuesWithNames.Item2}");
Console.WriteLine($"Third item: {valuesWithNames.Item3}");
Обратите внимание, что при назначении имен в правой части оператора должно использоваться ключевое слово var
для объявления переменной. Установка типов данных специальным образом (даже без специфических имен) заставляет компилятор применять синтаксис в левой части оператора, назначать свойствам имена согласно системе обозначений ItemX
и игнорировать имена, указанные в правой части. В следующих двух операторах имена Custom1
и Custom2
игнорируются:
(int, int) example = (Custom1:5, Custom2:7);
(int Field1, int Field2) example = (Custom1:5, Custom2:7);
Важно также понимать, что специальные имена полей существуют только на этапе компиляции и не доступны при инспектировании кортежа во время выполнения с использованием рефлексии (рефлексия раскрывается в главе 17).
Кортежи также могут быть вложенными как кортежи внутри кортежей. Поскольку с каждым свойством в кортеже связан тип данных, и кортеж является типом данных, следующий код полностью законен:
Console.WriteLine("=> Nested Tuples");
var nt = (5, 4, ("a", "b"));
Использование выведенных имен переменных (обновление в версии C# 7.1)
В C# 7.1 появилась возможность выводить имена переменных кортежей, как показано ниже:
Console.WriteLine("=> Inferred Tuple Names");
var foo = new {Prop1 = "first", Prop2 = "second"};
var bar = (foo.Prop1, foo.Prop2);
Console.WriteLine($"{bar.Prop1};{bar.Prop2}");
Понятие эквивалентности/неэквивалентности кортежей (нововведение в версии 7.3)
Дополнительным средством в версии C# 7.1 является эквивалентность (==
) и неэквивалентность (!=
) кортежей. При проверке на неэквивалентность операции сравнения будут выполнять неявные преобразования типов данных внутри кортежей, включая сравнение допускающих и не допускающих null
кортежей и/или свойств. Это означает, что следующие проверки нормально работают, несмотря на разницу между int
и long
:
Console.WriteLine("=> Tuples Equality/Inequality");
// Поднятые преобразования
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Тоже True
// Преобразованным типом слева является (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Тоже True
// Преобразования выполняются с кортежами (long, long)
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Тоже True
Кортежи, которые содержат кортежи, также можно сравнивать, но только если они имеют одну и ту же форму. Нельзя сравнивать кортеж с тремя свойствами int
и кортеж, содержащий два свойства int
плюс кортеж.
Использование отбрасывания с кортежами
Ранее в главе для возвращения из вызова метода более одного значения применялись параметры out
. Для этого существуют другие способы вроде создания класса или структуры специально для возвращения значений. Но если такой класс или структура используется только в целях передачи данных для одного метода, тогда нет нужды выполнять излишнюю работу и писать добавочный код. Кортежи прекрасно подходят для решения задачи, т.к. они легковесны, просты в объявлении и несложны в применении.