Como escolher entre tipos anônimos e tipos de tupla

Escolher o tipo apropriado passa por considerar sua usabilidade, desempenho e compensações em comparação com outros tipos. Tipos anônimos estão disponíveis desde o C# 3.0, enquanto tipos genéricos System.Tuple<T1,T2> foram introduzidos com .NET Framework 4.0. Desde então, novas opções foram introduzidas com suporte ao nível da linguagem, 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 ou outro.

Usabilidade e funcionalidade

Tipos anônimos foram introduzidos no C# 3.0 com expressões LINQ (consulta integrada à linguagem). 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 cria uma instância de uma matriz de objetos DateTime e itera por meio 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}");
}

Tipos anônimos são instanciados usando o operador new e os nomes e tipos de propriedade são inferidos da declaração. Se dois ou mais inicializadores de objeto anônimos em um assembly especificarem uma sequência de propriedades que estão na mesma ordem e que têm os mesmos nomes e tipos, o compilador tratará os objetos como instâncias do mesmo tipo. Eles compartilham o mesmo tipo de informação gerado pelo compilador.

O snippet de C# anterior projeta um tipo anônimo com duas propriedades, assim como 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 pela consulta, assim como os tipos anônimos o fazem. Considere agora o seguinte exemplo usando 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 System.Tuple<T1,T2>, a instância expõe propriedades de item numerados, como Item1 e Item2. Esses nomes de propriedade podem tornar mais difícil entender a intenção dos valores da propriedade, pois o nome da propriedade fornece apenas o ordinal. Além disso, os tipos System.Tuple são tipos de referência class. No entanto, System.ValueTuple<T1,T2> é um tipo de valor struct. O snippet C# a seguir usa ValueTuple<string, long> para projetar. Ao fazer isso, ele atribui o uso de 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 Tipos de tupla (referência de C#) ou Tuplas (Visual Basic).

Os exemplos anteriores são todos funcionalmente equivalentes; no entanto, há pequenas diferenças em sua usabilidade e suas implementações subjacentes.

Compensações

Você pode querer usar sempre ValueTuple em vez de Tuple e tipos anônimos, mas há compensações que você deve considerar. Os tipos ValueTuple 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.

Principais diferenças

Nome Modificador de acesso Tipo Nome do membro personalizado Suporte à desconstrução Suporte à árvore de expressão
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 possa 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 relação a tipos anônimos ou tipos de tupla.

Desempenho

O desempenho entre esses tipos depende do cenário. O maior impacto envolve a compensação entre alocações e cópia. Na maioria dos cenários, o impacto é pequeno. Quando grandes impactos podem surgir, devem ser tomadas medidas para informar a decisão.

Conclusão

Como um desenvolvedor escolhendo entre tuplas e tipos anônimos, há vários fatores a serem considerados. De modo geral, se você não estiver trabalhando com árvores de expressão e estiver confortável com a sintaxe de tupla, escolha ValueTuple, pois elas fornecem um tipo de valor com flexibilidade para nomear propriedades. Se você estiver trabalhando com árvores de expressão e preferir nomear propriedades, escolha tipos anônimos. Caso contrário, use Tuple.

Confira também