Escolher entre tipos anónimos e tuplas
Escolher o tipo apropriado envolve considerar sua usabilidade, desempenho e compensações em comparação com outros tipos. Os tipos anônimos estão disponíveis desde o C# 3.0, enquanto os tipos genéricos System.Tuple<T1,T2> foram introduzidos com o .NET Framework 4.0. Desde então, novas opções foram introduzidas com suporte a nível de idioma, como System.ValueTuple<T1,T2> - que, como o nome indica, fornecem um tipo de valor com a flexibilidade de tipos anônimos. Neste artigo, você aprenderá quando é apropriado escolher um tipo em vez do outro.
Usabilidade e funcionalidade
Tipos anônimos foram introduzidos no C# 3.0 com expressões LINQ (Language-Integrated Query). Com o LINQ, os desenvolvedores geralmente projetam resultados de consultas em tipos anônimos que contêm algumas propriedades selecionadas dos objetos com os quais estão trabalhando. Considere o exemplo a seguir, que instancia uma matriz de DateTime objetos e itera através deles projetando em um tipo anônimo com duas propriedades.
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}");
}
Os tipos anônimos são instanciados usando o new
operador e os nomes e tipos de propriedade são inferidos da declaração. Se dois ou mais inicializadores de objeto anônimos no mesmo assembly especificarem uma sequência de propriedades que estão na mesma ordem e que têm os mesmos nomes e tipos, o compilador trata os objetos como instâncias do mesmo tipo. Eles compartilham as mesmas informações de tipo geradas pelo compilador.
O trecho anterior do C# projeta um tipo anônimo com duas propriedades, muito parecido com a seguinte classe C# gerada pelo compilador:
internal sealed class f__AnonymousType0
{
public string Formatted { get; }
public long Ticks { get; }
public f__AnonymousType0(string formatted, long ticks)
{
Formatted = formatted;
Ticks = ticks;
}
}
Para obter mais informações, consulte Tipos anônimos. A mesma funcionalidade existe com tuplas ao projetar em consultas LINQ, você pode selecionar propriedades em tuplas. Essas tuplas fluem através da consulta, assim como os tipos anônimos. Agora, considere o exemplo a seguir usando o 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}");
}
Com o System.Tuple<T1,T2>, a instância expõe propriedades de item numerado, como Item1
, e Item2
. Esses nomes de propriedade podem dificultar a compreensão da intenção dos valores de propriedade, já que o nome da propriedade fornece apenas o ordinal. Além disso, os tipos são tipos de System.Tuple
referência class
. O System.ValueTuple<T1,T2> no entanto, é um tipo de valor struct
. O trecho de C# a seguir, usa ValueTuple<string, long>
para projetar. Ao fazer isso, ele atribui usando uma sintaxe literal.
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}");
}
Para obter mais informações sobre tuplas, consulte Tuple types (C# reference) ou Tuples (Visual Basic).
Os exemplos anteriores são todos funcionalmente equivalentes, no entanto, há pequenas diferenças em sua usabilidade e suas implementações subjacentes.
Vantagens e desvantagens
Você pode querer sempre usar ValueTuple mais Tuplee tipos anônimos, mas há compensações que você deve considerar. Os ValueTuple tipos são mutáveis, enquanto Tuple são somente leitura. Tipos anônimos podem ser usados em árvores de expressão, enquanto tuplas não podem. A tabela a seguir é uma visão geral de algumas das principais diferenças.
Diferenças principais
Nome | Modificador de acesso | Type | Nome de membro personalizado | Apoio à desconstrução | Suporte à árvore de expressões |
---|---|---|---|---|---|
Tipos anónimos | internal |
class |
✔️ | ❌ | ✔️ |
Tuple | public |
class |
❌ | ❌ | ✔️ |
ValueTuple | public |
struct |
✔️ | ✔️ | ❌ |
Serialização
Uma consideração importante ao escolher um tipo, é se ele precisará ou não ser serializado. A serialização é o processo de conversão do estado de um objeto em um formulário que pode ser persistido ou transportado. Para obter mais informações, consulte serialização. Quando a serialização é importante, a criação de um class
ou struct
é preferível em vez de tipos anônimos ou tipos de tupla.
Desempenho
O desempenho entre esses tipos depende do cenário. O maior impacto envolve o tradeoff entre alocações e cópia. Na maioria dos cenários, o impacto é pequeno. Sempre que possam surgir impactos importantes, devem ser efetuadas medições para fundamentar a decisão.
Conclusão
Como um desenvolvedor que escolhe entre tuplas e tipos anônimos, há vários fatores a considerar. De um modo geral, se você não estiver trabalhando com árvores de expressão e estiver confortável com a sintaxe da tupla, escolha ValueTuple como elas fornecem um tipo de valor com a flexibilidade de nomear propriedades. Se você estiver trabalhando com árvores de expressão e preferir nomear propriedades, escolha tipos anônimos. Caso contrário, use Tuple.