Tipos de tupla (referência C#)

O recurso tuplas fornece sintaxe concisa para agrupar vários elementos de dados em uma estrutura de dados leve. O exemplo a seguir mostra como você pode declarar uma variável de tupla, inicializá-la e acessar seus membros de dados:

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.

(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

Como mostra o exemplo anterior, para definir um tipo de tupla, especifique os tipos de todos os seus membros de dados e, opcionalmente, os nomes dos campos. Você não pode definir métodos em um tipo de tupla, mas pode usar os métodos fornecidos pelo .NET, como mostra o exemplo a seguir:

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

Os tipos Tuple suportam operadores== de igualdade e !=. Para obter mais informações, consulte a seção Igualdade de tupla.

Os tipos de tupla são tipos de valor, os elementos de tupla são campos públicos. Isso torna as tuplas tipos de valores mutáveis .

Você pode definir tuplas com um grande número arbitrário de elementos:

var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Console.WriteLine(t.Item26);  // output: 26

Casos de uso de tuplas

Um dos casos de uso mais comuns de tuplas é como um tipo de retorno de método. Ou seja, em vez de definir out parâmetros de método, você pode agrupar resultados de método em um tipo de retorno de tupla, como mostra o exemplo a seguir:

int[] xs = [4, 7, 9];
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9

int[] ys = [-9, 0, 67, 100];
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100

(int min, int max) FindMinMax(int[] input)
{
    if (input is null || input.Length == 0)
    {
        throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
    }

    // Initialize min to MaxValue so every value in the input
    // is less than this initial value.
    var min = int.MaxValue;
    // Initialize max to MinValue so every value in the input
    // is greater than this initial value.
    var max = int.MinValue;
    foreach (var i in input)
    {
        if (i < min)
        {
            min = i;
        }
        if (i > max)
        {
            max = i;
        }
    }
    return (min, max);
}

Como mostra o exemplo anterior, você pode trabalhar com a instância de tupla retornada diretamente ou desconstruí-la em variáveis separadas.

Você também pode usar tipos de tupla em vez de tipos anônimos, por exemplo, em consultas LINQ. Para obter mais informações, consulte Escolhendo entre tipos anônimos e tuplas.

Normalmente, você usa tuplas para agrupar elementos de dados vagamente relacionados. Em APIs públicas, considere definir uma classe ou um tipo de estrutura .

Nomes de campos de tupla

Você especifica explicitamente nomes de campos de tupla em uma expressão de inicialização de tupla ou na definição de um tipo de tupla, como mostra o exemplo a seguir:

var t = (Sum: 4.5, Count: 3);
Console.WriteLine($"Sum of {t.Count} elements is {t.Sum}.");

(double Sum, int Count) d = (4.5, 3);
Console.WriteLine($"Sum of {d.Count} elements is {d.Sum}.");

Se você não especificar um nome de campo, ele poderá ser inferido a partir do nome da variável correspondente em uma expressão de inicialização de tupla, como mostra o exemplo a seguir:

var sum = 4.5;
var count = 3;
var t = (sum, count);
Console.WriteLine($"Sum of {t.count} elements is {t.sum}.");

Isso é chamado de inicializadores de projeção de tupla. O nome de uma variável não é projetado em um nome de campo de tupla nos seguintes casos:

  • O nome do candidato é um nome de membro de um tipo de tupla, por exemplo, Item3, ToStringou Rest.
  • O nome do candidato é uma duplicata de outro nome de campo de tupla, explícito ou implícito.

Nos casos anteriores, você especifica explicitamente o nome de um campo ou acessa um campo pelo nome padrão.

Os nomes padrão dos campos de tupla são Item1, Item3Item2e assim por diante. Você sempre pode usar o nome padrão de um campo, mesmo quando um nome de campo é especificado explicitamente ou inferido, como mostra o exemplo a seguir:

var a = 1;
var t = (a, b: 2, 3);
Console.WriteLine($"The 1st element is {t.Item1} (same as {t.a}).");
Console.WriteLine($"The 2nd element is {t.Item2} (same as {t.b}).");
Console.WriteLine($"The 3rd element is {t.Item3}.");
// Output:
// The 1st element is 1 (same as 1).
// The 2nd element is 2 (same as 2).
// The 3rd element is 3.

As comparações de atribuição de tupla e igualdade de tupla não levam em conta os nomes dos campos.

Em tempo de compilação, o compilador substitui nomes de campo não padrão pelos nomes padrão correspondentes. Como resultado, nomes de campo explicitamente especificados ou inferidos não estão disponíveis em tempo de execução.

Gorjeta

Habilite a regra de estilo de código .NET IDE0037 para definir uma preferência em nomes de campo de tupla inferidos ou explícitos.

A partir do C# 12, você pode especificar um alias para um tipo de tupla com uma using diretiva. O exemplo a seguir adiciona um global using alias para um tipo de tupla com dois valores inteiros para um valor e permitido MinMax :

global using BandPass = (int Min, int Max);

Depois de declarar o alias, você pode usar o BandPass nome como um alias para esse tipo de tupla:

BandPass bracket = (40, 100);
Console.WriteLine($"The bandpass filter is {bracket.Min} to {bracket.Max}");

Um alias não introduz um novo tipo, mas apenas cria um sinônimo para um tipo existente. Você pode desconstruir uma tupla declarada com o alias da mesma forma que pode com seu tipo de tupla BandPass subjacente:

(int a , int b) = bracket;
Console.WriteLine($"The bracket is {a} to {b}");

Tal como acontece com a atribuição ou desconstrução de tuplas, os nomes dos membros da tupla não precisam corresponder; os tipos sim.

Da mesma forma, um segundo alias com a mesma aridade e tipos de membros pode ser usado indistintamente com o alias original. Você pode declarar um segundo alias:

using Range = (int Minimum, int Maximum);

Você pode atribuir uma Range tupla a uma BandPass tupla. Como em toda atribuição de tupla, os nomes de campo não precisam corresponder, apenas os tipos e a aridade.

Range r = bracket;
Console.WriteLine($"The range is {r.Minimum} to {r.Maximum}");

Um alias para um tipo de tupla fornece mais informações semânticas quando você usa tuplas. Não introduz um novo tipo. Para fornecer segurança de tipo, você deve declarar um posicional record .

Atribuição e desconstrução de tuplas

O C# suporta a atribuição entre tipos de tupla que satisfazem ambas as seguintes condições:

  • Ambos os tipos de tupla têm o mesmo número de elementos
  • para cada posição da tupla, o tipo do elemento da tupla direita é o mesmo ou implicitamente convertível para o tipo do elemento da tupla esquerda correspondente

Os valores dos elementos de tupla são atribuídos seguindo a ordem dos elementos de tupla. Os nomes dos campos de tupla são ignorados e não atribuídos, como mostra o exemplo a seguir:

(int, double) t1 = (17, 3.14);
(double First, double Second) t2 = (0.0, 1.0);
t2 = t1;
Console.WriteLine($"{nameof(t2)}: {t2.First} and {t2.Second}");
// Output:
// t2: 17 and 3.14

(double A, double B) t3 = (2.0, 3.0);
t3 = t2;
Console.WriteLine($"{nameof(t3)}: {t3.A} and {t3.B}");
// Output:
// t3: 17 and 3.14

Você também pode usar o operador = de atribuição para desconstruir uma instância de tupla em variáveis separadas. Você pode fazer isso de várias maneiras:

  • Use a var palavra-chave fora dos parênteses para declarar variáveis digitadas implicitamente e permitir que o compilador infera seus tipos:

    var t = ("post office", 3.6);
    var (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Declare explicitamente o tipo de cada variável entre parênteses:

    var t = ("post office", 3.6);
    (string destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Declare alguns tipos explicitamente e outros tipos implicitamente (com var) entre parênteses:

    var t = ("post office", 3.6);
    (var destination, double distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    
  • Use variáveis existentes:

    var destination = string.Empty;
    var distance = 0.0;
    
    var t = ("post office", 3.6);
    (destination, distance) = t;
    Console.WriteLine($"Distance to {destination} is {distance} kilometers.");
    // Output:
    // Distance to post office is 3.6 kilometers.
    

O destino de uma expressão de desconstrução pode incluir variáveis existentes e variáveis declaradas na declaração de desconstrução.

Você também pode combinar desconstrução com correspondência de padrões para inspecionar as características dos campos em uma tupla. O exemplo a seguir percorre vários inteiros e imprime aqueles que são divisíveis por 3. Desconstrói o resultado da tupla e Int32.DivRem os jogos contra a Remainder de 0:

for (int i = 4; i < 20;  i++)
{
    if (Math.DivRem(i, 3) is ( Quotient: var q, Remainder: 0 ))
    {
        Console.WriteLine($"{i} is divisible by 3, with quotient {q}");
    }
}

Para obter mais informações sobre a desconstrução de tuplas e outros tipos, consulte Desconstruindo tuplas e outros tipos.

Igualdade tupla

Os tipos de tupla suportam os == e != operadores. Esses operadores comparam os membros do operando esquerdo com os membros correspondentes do operando direito seguindo a ordem dos elementos de tupla.

(int a, byte b) left = (5, 10);
(long a, int b) right = (5, 10);
Console.WriteLine(left == right);  // output: True
Console.WriteLine(left != right);  // output: False

var t1 = (A: 5, B: 10);
var t2 = (B: 5, A: 10);
Console.WriteLine(t1 == t2);  // output: True
Console.WriteLine(t1 != t2);  // output: False

Como mostra o exemplo anterior, as == operações e != não levam em conta nomes de campos de tupla.

Duas tuplas são comparáveis quando estão preenchidas ambas as seguintes condições:

  • Ambas as tuplas têm o mesmo número de elementos. Por exemplo, t1 != t2 não compila se t1 e t2 tem números diferentes de elementos.
  • Para cada posição de tupla, os elementos correspondentes dos operandos de tupla esquerda e direita são comparáveis com os == operadores e != . Por exemplo, (1, (2, 3)) == ((1, 2), 3) não compila porque 1 não é comparável com (1, 2)o .

Os == operadores comparam != tuplas em curto-circuito. Ou seja, uma operação para assim que encontra um par de elementos não iguais ou atinge as extremidades das tuplas. No entanto, antes de qualquer comparação, todos os elementos da tupla são avaliados, como mostra o exemplo a seguir:

Console.WriteLine((Display(1), Display(2)) == (Display(3), Display(4)));

int Display(int s)
{
    Console.WriteLine(s);
    return s;
}
// Output:
// 1
// 2
// 3
// 4
// False

Tuplas como parâmetros de saída

Normalmente, você refatora um método que tem out parâmetros em um método que retorna uma tupla. No entanto, há casos em que um out parâmetro pode ser do tipo tupla. O exemplo a seguir mostra como trabalhar com tuplas como out parâmetros:

var limitsLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (limitsLookup.TryGetValue(4, out (int Min, int Max) limits))
{
    Console.WriteLine($"Found limits: min is {limits.Min}, max is {limits.Max}");
}
// Output:
// Found limits: min is 10, max is 20

Tuplas vs System.Tuple

As tuplas C#, que são apoiadas por System.ValueTuple tipos, são diferentes das tuplas que são representadas por System.Tuple tipos. As principais diferenças são as seguintes:

  • System.ValueTupletipos são tipos de valor. System.Tupletipos são tipos de referência.
  • System.ValueTuple os tipos são mutáveis. System.Tuple os tipos são imutáveis.
  • Os membros de dados dos System.ValueTuple tipos são campos. Os membros de dados dos System.Tuple tipos são propriedades.

Especificação da linguagem C#

Para obter mais informações, consulte:

Consulte também