Выбор между анонимными типами и кортежами
При выборе подходящего типа необходимо учитывать его применимость, производительность и недостатки по сравнению с другими типами. Анонимные типы были представлены в C# 3.0, а универсальные типы System.Tuple<T1,T2> — в .NET Framework 4.0. После этого появились новые поддерживаемые на уровне языка параметры, например System.ValueTuple<T1,T2>, который, как следует из его имени, реализует тип значения, обладающий универсальностью анонимного типа. В этой статье мы расскажем о том, в каких случаях следует выбирать тот или иной тип.
Применимость и функциональность
Анонимные типы были представлены в C# 3.0 вместе с выражениями LINQ. С помощью LINQ разработчики часто проецируют результаты запросов в анонимные типы, содержащие некоторые из свойств объектов, с которыми они работают. Рассмотрим следующий пример, где создается экземпляр массива объектов DateTime, которые посредством итераций проецируются в анонимный тип с двумя свойствами.
var dates = new[]
{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};
foreach (var anonymous in
dates.Select(
date => new { Formatted = $"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks }))
{
Console.WriteLine($"Ticks: {anonymous.Ticks}, formatted: {anonymous.Formatted}");
}
Экземпляры анонимных типов создаются с помощью оператора new
, а имена свойств и типы при этом выводятся из объявления. Если несколько инициализаторов анонимных объектов в одной сборке указывают на последовательность свойств, идущих в том же порядке и имеющих те же типы и имена, компилятор обрабатывает объекты как экземпляры одного типа. Они используют одни и те же сведения типа, созданные компилятором.
В приведенном выше фрагменте кода C# выполняется проецирование в анонимный тип с двумя свойствами, что во многом аналогично создаваемому компилятором классу C#:
internal sealed class f__AnonymousType0
{
public string Formatted { get; }
public long Ticks { get; }
public f__AnonymousType0(string formatted, long ticks)
{
Formatted = formatted;
Ticks = ticks;
}
}
Дополнительные сведения см. в статье Анонимные типы. Аналогичная ситуация складывается с кортежами: при проецировании в запросы LINQ вы можете выбирать свойства для кортежа. Такие кортежи обрабатываются в рамках запроса так же, как и анонимные типы. Рассмотрим следующий пример, в котором используется System.Tuple<string, long>
.
var dates = new[]
{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};
foreach (var tuple in
dates.Select(
date => new Tuple<string, long>($"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {tuple.Item2}, formatted: {tuple.Item1}");
}
С помощью System.Tuple<T1,T2> экземпляр предоставляет свойства нумерованного элемента, такие как Item1
и Item2
. При использовании таких имен свойств, которые содержат только порядковые номера, сложно понять предназначение их значений. Кроме того, типы System.Tuple
являются ссылочными типами class
. Тем не менее, System.ValueTuple<T1,T2> является типом значения struct
. В следующем фрагменте кода C# выполняется проецирование в ValueTuple<string, long>
. При этом назначение осуществляется с использованием литерального синтаксиса.
var dates = new[]
{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};
foreach (var (formatted, ticks) in
dates.Select(
date => (Formatted: $"{date:MMM dd, yyyy at hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {ticks}, formatted: {formatted}");
}
Дополнительные сведения о кортежах см. в статьях Типы кортежей (справочник по C#) или Кортежи (Visual Basic).
В предыдущих примерах все функционально эквивалентны, однако существуют незначительные различия в их удобствах и их базовых реализациях.
Компромиссы
Вы можете во всех случаях использовать ValueTuple вместо Tuple и анонимные типы, однако при этом необходимо учитывать свойственные им недостатки. Типы ValueTuple являются изменяемыми, тогда как типы Tuple доступны только для чтения. В отличие от кортежей, анонимные типы можно использовать в деревьях выражений. В следующей таблице представлен обзор некоторых основных различий между ними.
Основные отличия
Имя. | Модификатор доступа | Тип | Имя пользовательского элемента | Поддержка деконструирования | Поддержка деревьев выражений |
---|---|---|---|---|---|
Анонимные типы | internal |
class |
✔️ | ❌ | ✔️ |
Tuple | public |
class |
❌ | ❌ | ✔️ |
ValueTuple | public |
struct |
✔️ | ✔️ | ❌ |
Сериализация
При выборе типа важно учитывать необходимость в его сериализации. Сериализация представляет собой процесс преобразования состояния объекта в форму, пригодную для сохранения или передачи. Дополнительные сведения см. в статье Сериализация. Если сериализация нужна, вместо анонимных типов или кортежей рекомендуется создавать class
или struct
.
Производительность
Различия в производительности между этими типами зависят от сценария их применения. Основным фактором, влияющим на производительность, является достижение компромисса между возможностями выделения памяти и копирования. В большинстве случаев это влияние незначительно. Тем не менее, если возникают проблемы, следует выполнить измерения, по результатам которых будет приниматься взвешенное решение.
Заключение
При выборе между анонимными типами и кортежами разработчику необходимо учитывать ряд факторов. В общем случае, если вы не работаете с деревьями выражений и хорошо знакомы с синтаксисом кортежей, мы рекомендуем выбирать типы ValueTuple, которые предоставляют тип значений с возможностью именования свойств. Если вы работаете с деревьями выражений и предпочитаете именованные свойства, следует выбирать анонимные типы. Или используйте Tuple.