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();
Все анонимные типы с одинаковыми свойствами и в том же порядке считаются одинаковыми типами.