Partilhar via


Tipos de tupla (referência C#)

A funcionalidade de tuplas fornece uma sintaxe concisa para agrupar múltiplos elementos de dados numa estrutura de dados leve.

A referência da linguagem C# documenta a versão mais recentemente lançada da linguagem C#. Contém também documentação inicial para funcionalidades em pré-visualizações públicas para o próximo lançamento linguístico.

A documentação identifica qualquer funcionalidade introduzida pela primeira vez nas últimas três versões da língua ou em pré-visualizações públicas atuais.

Gorjeta

Para saber quando uma funcionalidade foi introduzida pela primeira vez em C#, consulte o artigo sobre o histórico de versões da linguagem C#.

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, especifica-se 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. Esse design torna as tuplas tipos de valor mutáveis .

Pode definir tuplas com um número arbitrariamente grande 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 para tuplas

Um dos casos de uso mais comuns para tuplas é como tipo de retorno de método. Em vez de definir out parâmetros do método, o método de grupo resulta num tipo de retorno de tupla, como mostra o exemplo seguinte:

int[] xs = new int[] { 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 = new int[] { -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, usam-se 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

Especifica explicitamente os nomes dos campos de tuplas numa expressão de inicialização de tupla ou na definição de um tipo de tupla, como mostra o exemplo seguinte:

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 não especificar um nome de campo, o compilador pode inferi-lo a partir do nome da variável correspondente numa expressão de inicialização de tupla, como mostra o exemplo seguinte:

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

Esta característica chama-se inicializadores de projeção de tuplas. 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 tuplas são Item1, Item2, Item3, e assim sucessivamente. 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 tuplas têm o mesmo número de elementos.
  • Para cada posição da tupla, o tipo do elemento da tupla à direita é igual ou implicitamente convertível ao tipo do elemento correspondente da tupla à esquerda.

Atribuir valores dos elementos da tuplas seguindo a ordem dos elementos da tupla. O processo de atribuição ignora os nomes dos campos de tuplas, como mostra o seguinte exemplo:

(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

Use 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 têm em conta os nomes dos campos de tuplas.

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 da tupla, os elementos correspondentes dos operandos da tupla esquerda e direita são comparáveis usando 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 e != comparam tuplas de forma a 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, existem alguns casos em que um out parâmetro pode ser um tipo de 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 em C# usam System.ValueTuple tipos e diferem das tuplas que usam System.Tuple tipos. As principais diferenças são:

  • 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