Перейти к основному содержимому

5.05. Анонимные типы и кортежи

Разработчику Архитектору

Анонимные типы и кортежи

В реальной разработке часто возникает потребность временно объединить несколько значений — например, вернуть из метода не только результат, но и флаг успеха, или передать пару значений между методами. Создавать для этого отдельный класс — избыточно. На помощь приходят кортежи (tuples) и анонимные типы. Они позволяют писать лаконичный, читаемый код, не жертвуя типизацией.

Кортеж (tuple) — это легковесная структура, содержащая фиксированное количество элементов разных типов, объединённых в одно значение. Кортежи не имеют методов, поведения или наследования — они просто хранят данные.

Это своего рода контейнер из нескольких значений, к примеру (1, "John", true) это один объект, содержащий int, string и bool.

Соответственно, они нужны, чтобы вернуть несколько значений из метода, временно хранить связанные данные, упрощать код вместо множества переменных, передавать данные между методами без создания класса и для деструктуризации (разбора на отдельные переменные).

Кортежи появились в C# 7.0 на основе ValueTuple. Они значимые типы (struct), что делает их быстрыми и эффективными.

Анонимные кортежи (без имён полей) создаются с помощью круглых скобок:

var person = (1, "Alice", 25);

Тип: (int, string, int). Доступ к элементам: через Item1, Item2, Item3...

Console.WriteLine(person.Item1); // 1
Console.WriteLine(person.Item2); // "Alice"
Console.WriteLine(person.Item3); // 25

Недостаток: имена Item1, Item2 — не говорят о смысле данных.

Поэтому можно задать свои имена элементов - это именованные кортежи:

var person = (Id: 1, Name: "Alice", Age: 25);

Теперь доступ через понятные имена:

Console.WriteLine(person.Name); // "Alice"
Console.WriteLine(person.Age); // 25

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

var p1 = (Id: 1, Name: "A");
var p2 = (Id: 2, Name: "B");
p1 = p2; // OK — одинаковая структура

C# умеет выводить тип кортежа:

var result = (x: 10, y: 20); // тип: (int x, int y)

Но можно указать явно:

(int x, int y) point = (10, 20);

Или даже с разными способами инициализации:

(int Id, string Name) user = (id: 1, name: "Bob"); // OK, имена параметров не обязаны совпадать

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

var person = (Id: 1, Name: "Alice", Age: 25);

// Деструктуризация
var (id, name, age) = person;
Console.WriteLine($"{name}, {age} лет"); // Alice, 25 лет

Можно игнорировать ненужные поля:

var (id, _, age) = person; // игнорируем Name

Использование с out-параметрами:

if (int.TryParse("123", out var result))
{
// result — распакованный int
}

В циклах (например, с Dictionary):

var dict = new Dictionary<string, int> { {"a", 1}, {"b", 2} };

foreach (var (key, value) in dict)
{
Console.WriteLine($"{key}: {value}");
}

Вернуть несколько значений:

public (bool Success, string Message, int Code) Validate(string input)
{
if (string.IsNullOrWhiteSpace(input))
return (false, "Input is empty", 400);

return (true, "OK", 200);
}

// Использование:
var result = Validate("test");
if (result.Success)
{
Console.WriteLine(result.Message);
}

Деструктуризация при вызове:

var (success, msg, code) = Validate("hello");
Console.WriteLine(success ? "OK" : msg);

Кортежи в C# 7+ основаны на структуре System.ValueTuple. ValueTuple<T1> — для 1 элемента, ValueTuple<T1, T2> — для 2. До 8 элементов напрямую. Если больше — 8-й элемент может быть ValueTuple (вложенный).

var bigTuple = (1, 2, 3, 4, 5, 6, 7, (8, 9)); // 9 элементов

Преимущества ValueTuple перед старым Tuple в том, что он значимый тип, и меньше нагружает GC, поддерживает имена полей, деструктуризацию и лучшую производительность. Старый Tuple (из .NET Framework) — ссылочный тип, не поддерживает имена и деструктуризацию.

Анонимные типы — это временные классы, создаваемые компилятором на лету. Они используются, когда нужно создать объект с определёнными свойствами, не объявляя отдельный класс.

var person = new { Name = "John", Age = 30 };

Компилятор создаёт неименованный класс с public readonly свойствами. Тип: выводится автоматически (var обязателен). Область видимости — в пределах метода (нельзя передавать в другие методы напрямую).

var user = new { Id = 1, Name = "Alice", IsActive = true };

Console.WriteLine(user.Name); // OK
// user.Name = "Bob"; // ОШИБКА — свойства только для чтения

Чаще всего можно увидеть в LINQ-запросах:

var results = users
.Where(u => u.Age > 18)
.Select(u => new { u.Name, u.Email })
.ToList();

Все анонимные типы с одинаковыми свойствами и в том же порядке считаются одинаковыми типами.