Compartilhar via


Tuplas e desconstrução

Dica

Novo no desenvolvimento de software? Comece com os tutoriais de Introdução . Você encontrará tuplas quando precisar retornar vários valores de um método ou agrupar valores sem definir um tipo nomeado.

Experimentou em outro idioma? Tuplas C# são tipos de valor semelhantes a tuplas em Python ou Swift, mas com elementos nomeados opcionais e suporte completo à desconstrução. Percorra as seções de desconstrução e igualdade para encontrar padrões específicos de C#.

Uma tupla agrupa vários valores em uma única estrutura leve sem exigir que você defina um tipo nomeado. Tuplas são tipos de valor que você pode declarar diretamente, retornar por métodos e desconstruir em variáveis individuais. Use tuplas quando precisar de um agrupamento rápido e temporário de valores relacionados. Por exemplo, quando você retorna vários resultados de um método ou armazena um par de coordenadas.

O exemplo a seguir cria uma tupla com elementos nomeados e acessa cada elemento por nome:

var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321

Tuplas funcionam bem para agrupamentos de curta duração em que definir uma classe, estrutura ou registro adicionaria formalidade desnecessária. Para conceitos de domínio de longa duração ou tipos com comportamento, prefira registros, classes ou structs. Para obter uma comparação de quando usar cada um, consulte Escolher qual tipo de tipo.

Declarar e inicializar tuplas

Declare uma tupla listando os tipos dos elementos em parênteses. Opcionalmente, você pode nomear cada elemento para tornar o código mais legível:

// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");

// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");

// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");

Quando você não fornece nomes, os elementos usam nomes padrão Item1, Item2, e assim por diante. Os elementos nomeados fazem com que seu código seja auto-documentado sem exigir uma definição de tipo separada.

Nomes de elementos inferidos

O compilador infere nomes de elementos dos nomes de variáveis ou nomes de propriedade que você usa para inicializar a tupla. Esse recurso evita a redundância quando os nomes correspondem:

var name = "Carol";
var age = 28;

// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28

Os nomes inferidos mantêm seu código conciso. Se você precisar de um nome de elemento diferente, especifique-o explicitamente.

Retornar vários valores de um método

Um dos usos mais comuns para tuplas é retornar vários valores de um método. Em vez de definir uma classe ou usar out parâmetros, retorne uma tupla com elementos nomeados:

static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
    var min = values.Min();
    var max = values.Max();
    var avg = values.Average();
    return (min, max, avg);
}

Os elementos de tupla nomeados tornam os valores retornados legíveis tanto no local de chamada quanto na assinatura do método. O chamador pode acessar cada valor pelo nome sem precisar se lembrar da ordem posicional.

Desconstruir tuplas

A desconstrução descompacta os elementos de uma tupla em variáveis separadas em uma única instrução. Você pode desconstruir tuplas de várias maneiras:

var point = (X: 3, Y: 7);

// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");

// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");

// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");

// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");

A desconstrução é especialmente útil quando você recebe uma tupla de uma chamada de método e imediatamente precisa trabalhar com os seus valores individuais.

Você pode desconstruir tuplas diretamente em laços foreach, o que torna a iteração sobre coleções de valores agrupados concisa.

List<(string Name, int Score)> results =
[
    ("Alice", 92),
    ("Bob", 87),
    ("Carol", 95)
];

foreach (var (name, score) in results)
{
    Console.WriteLine($"{name}: {score}");
}

Quando você não precisar de todos os elementos, use um descarte (_) no lugar de cada valor que você deseja ignorar. Use um _ separado para cada posição descartada:

List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15

Para obter mais informações sobre como usar descartes em diferentes contextos, consulte Descartes.

Igualdade de tupla

Você pode comparar tuplas usando == e !=. Esses operadores comparam cada elemento em ordem, portanto, duas tuplas são iguais quando todos os seus elementos correspondentes são iguais:

var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);

Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False

// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True

A igualdade de tupla usa o operador == definido em cada tipo de elemento, o que significa que a comparação funciona corretamente para cadeias, números e outros tipos que definem ==. Os nomes de elementos não afetam a igualdade, apenas os valores e as posições importam.

Mutação não destrutiva com with

A with expressão cria uma cópia de uma tupla com um ou mais elementos alterados, deixando o original inalterado:

var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };

Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99

Esse padrão é útil quando você deseja uma variação de uma tupla existente sem modificar o original. A with expressão funciona da mesma maneira em tuplas como nos registros.

Tuplas em dicionários e consultas

As tuplas fazem valores convenientes para dicionários quando você precisa associar uma chave a vários conjuntos de dados.

var sizeChart = new Dictionary<string, (int Min, int Max)>
{
    ["Small"] = (0, 50),
    ["Medium"] = (51, 100),
    ["Large"] = (101, 200)
};

if (sizeChart.TryGetValue("Medium", out var range))
{
    Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100

As tuplas também funcionam como chaves de dicionário, permitindo uma chave composta sem definir um tipo personalizado. Como as tuplas implementam a igualdade estrutural, as consultas correspondem aos valores combinados de todos os elementos:

var grid = new Dictionary<(int Row, int Column), string>
{
    [(0, 0)] = "Origin",
    [(1, 3)] = "Sensor A",
    [(2, 5)] = "Sensor B"
};

var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
    Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A

Esse padrão evita a necessidade de uma classe separada para buscas simples com múltiplas chaves ou para mapeamentos de uma chave para múltiplos valores.

Tuplas versus tipos anônimos

Tuplas são a escolha preferida quando você precisa de uma estrutura de dados leve sem nome. Os tipos anônimos permanecem disponíveis para cenários de árvore de expressão e para código que exige tipos de referência, mas as tuplas oferecem melhor desempenho, suporte para desconstrução e sintaxe mais flexível. Para obter mais informações sobre tipos anônimos, consulte Escolher entre tipos anônimos e tuplas.

Consulte também