Como escolher entre tipos anônimos e tipos de tupla

Escolha entre tipos anônimos System.Tuple<T1,T2>e System.ValueTuple<T1,T2> , com base na usabilidade, desempenho e compensações. ValueTuple fornece um tipo de valor com a flexibilidade de tipos anônimos. Este artigo explica quando escolher cada tipo.

Usabilidade e funcionalidade

Com a consulta Language-Integrated (LINQ), muitas vezes você projeta resultados de consulta em tipos anônimos que contêm algumas propriedades selecionadas dos objetos de origem. O exemplo a seguir cria uma instância de uma matriz de DateTime objetos e projeta cada objeto 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 new operador e os nomes e tipos de propriedade são inferidos da declaração. Se dois ou mais inicializadores de objetos anônimos no mesmo assembly especificarem uma sequência de propriedades que estão na mesma ordem e que tenham 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ódigo 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. 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 System.Tuple<T1,T2>, a instância revela propriedades de itens numeradas, 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, tipos System.Tuple são tipos class de referência. Entretanto, o 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 Tuple types (referência C#) 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.

Compensações

Talvez você queira sempre usar ValueTuple em vez de Tuple e tipos anônimos, mas há desvantagens que você deve considerar. Os ValueTuple tipos são mutáveis, enquanto Tuple são somente de 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. Serialização é o processo de converter o 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 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, medidas devem ser tomadas 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.

Consulte também