Поделиться через


Кортежи и анонимные типы

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

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

Кортежи и анонимные типы

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

Функция Анонимные типы Кортежи
Тип Ссылочный тип (class) Тип значения (struct)
Performance Выделение кучи Выделение стека (более высокая производительность)
Mutability Свойства только для чтения Изменяемые поля
Деконструкция Не поддерживается Поддерживается
Деревья выражений Поддерживается Не поддерживается
Модификатор доступа internal public
Имена членов Обязательный или предполагаемый Необязательный (с именами по умолчанию, например Item1, Item2)

Когда следует использовать кортежи

Используйте кортежи, когда:

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

В следующем примере показано, как кортежи обеспечивают аналогичную функциональность анонимным типам с более чистым синтаксисом:

// Tuple with named elements.
var tupleProduct = (Name: "Widget", Price: 19.99M);
Console.WriteLine($"Tuple: {tupleProduct.Name} costs ${tupleProduct.Price}");

// Equivalent example using anonymous types.
var anonymousProduct = new { Name = "Widget", Price = 19.99M };
Console.WriteLine($"Anonymous: {anonymousProduct.Name} costs ${anonymousProduct.Price}");

Деконструкция кортежа

Вы можете разбить кортеж на отдельные переменные, что позволяет удобно работать с отдельными элементами кортежа. C# поддерживает несколько способов деконструкции кортежей:

static (string Name, int Age, string City) GetPersonInfo()
{
    return ("Alice", 30, "Seattle");
}
// Deconstruct using var for all variables
var (name, age, city) = GetPersonInfo();
Console.WriteLine($"{name} is {age} years old and lives in {city}");
// Output: Alice is 30 years old and lives in Seattle

// Deconstruct with explicit types
(string personName, int personAge, string personCity) = GetPersonInfo();
Console.WriteLine($"{personName}, {personAge}, {personCity}");

// Deconstruct into existing variables
string existingName;
int existingAge;
string existingCity;
(existingName, existingAge, existingCity) = GetPersonInfo();

// Deconstruct and discard unwanted values using the discard pattern (_)
var (name2, _, city2) = GetPersonInfo();
Console.WriteLine($"{name2} lives in {city2}");
// Output: Alice lives in Seattle

Деконструкция полезна в циклах и сценариях сопоставления шаблонов:

var people = new List<(string Name, int Age)>
{
    ("Bob", 25),
    ("Carol", 35),
    ("Dave", 40)
};

foreach (var (personName2, personAge2) in people)
{
    Console.WriteLine($"{personName2} is {personAge2} years old");
}

Кортежи в качестве возвращаемого типа метода

Распространенный вариант использования кортежей — это тип возвращаемого значения метода. Вместо определения out параметров, можно группировать результаты метода в кортеже. Не удается вернуть анонимный тип из метода, так как он не имеет имени, а тип возвращаемого значения не может быть объявлен.

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

var configLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (configLookup.TryGetValue(4, out (int Min, int Max) range))
{
    Console.WriteLine($"Found range: min is {range.Min}, max is {range.Max}");
}
// Output: Found range: min is 10, max is 20

Этот шаблон полезен при работе с методами, которые должны возвращать индикатор успешности и несколько значений результатов. Кортеж позволяет использовать именованные поля (Min и Max) вместо универсальных имен, таких как Item1 и Item2, что делает код более читаемым и самодокументируемым.

Когда следует использовать анонимные типы

Используйте анонимные типы, когда:

  • Вы работаете с деревьями выражений (например, в некоторых поставщиках Microsoft Language-Integrated Query (LINQ)).
  • Объект должен быть ссылочным типом.

Наиболее частый сценарий — это инициализация анонимного типа со свойствами из другого типа. В следующем примере предполагается, что существует класс с именем Product. Класс Product включает Color и Price свойства вместе с другими свойствами, которые вам не нужны:

class Product
{
    public string? Color { get; init; }
    public decimal Price { get; init; }
    public string? Name { get; init; }
    public string? Category { get; init; }
    public string? Size { get; init; }
}

Объявление анонимного new типа начинается с оператора, используемого вместе с инициализатором объекта. Объявление инициализирует новый тип, который использует только два свойства из Product. Анонимные типы обычно используются в select предложении выражения запроса для возврата меньшего объема данных. Дополнительные сведения о запросах см. в разделе о LINQ в C#.

Если имена участников в анонимном типе не указаны, компилятор предоставляет членам анонимного типа то же имя, что и свойство, используемое для их инициализации. Укажите имя свойства, инициализируемого выражением, как показано в предыдущем примере.

В следующем примере используются имена свойств анонимного типа Color и Price. Экземпляры коллекции products являются элементами типов Product:

var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

Инициализаторы проекции с анонимными типами

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

// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };

// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };

// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");

Этот упрощенный синтаксис полезен при создании анонимных типов со многими свойствами:

var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;

// Using projection initializers.
var employee = new { title, department, salary };

// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };

Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");

Имя члена не выводится в следующих случаях:

  • Имя кандидата совпадает с именем другого элемента свойства в том же анонимном типе, либо явным, либо неявным образом.
  • Имя кандидата не является допустимым идентификатором (например, содержит пробелы или специальные символы).

В этих случаях необходимо явно указать имя члена.

Совет

Правило стиля .NET можно использовать IDE0037 для принудительного применения выводимых или явных имен элементов.

Можно также определить поле с помощью объекта другого типа: класса, структуры или даже другого анонимного типа. Для этого используйте переменную, содержащую этот объект. В следующем примере показаны два анонимных типа, которые используют уже созданные пользователем типы. В обоих случаях поле в анонимных типах shipment и shipmentWithBonus является типа Product и содержит значения по умолчанию для каждого поля. Поле bonus имеет анонимный тип, созданный компилятором.

var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };

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

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

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

Анонимные типы являются class типами, производными непосредственно от object, и их нельзя привести к любому типу, кроме object. Компилятор предоставляет имя для каждого анонимного типа, хотя приложение не может получить к нему доступ. С точки зрения среды CLR анонимный тип не отличается от других ссылочных типов.

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

Анонимные типы поддерживают неразрушительную мутацию в виде выражений. Эта функция позволяет создать новый экземпляр анонимного типа, где одно или несколько свойств имеют новые значения:

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

Невозможно объявить поле, свойство, событие или тип возвращаемого метода как анонимный тип. Аналогичным образом нельзя объявить формальный параметр метода, свойства, конструктора или индексатора как анонимный тип. Чтобы передать анонимный тип или коллекцию, содержащую анонимные типы, в качестве аргумента в метод, можно объявить параметр как тип object. Однако использование object для анонимных типов побеждает цель строгого ввода. Если вам нужно сохранить результаты запроса или передать их за пределы метода, используйте вместо анонимного типа структуру или класс, названные обычным образом.

Так как методы Equals и GetHashCode в анонимных типах определяются через методы Equals и GetHashCode свойств, два экземпляра одного и того же анонимного типа равны, только если равны их свойства.

Примечание.

Уровень доступности анонимного типа internal. Поэтому два анонимных типа, определенных в разных сборках, не совпадают. Таким образом, экземпляры анонимных типов не могут быть равны друг другу при определении в разных сборках, даже если все их свойства равны.

Анонимные типы переопределяют ToString метод, сцепляя имя и ToString выходные данные каждого свойства, окруженного фигурными скобками.

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

См. также