Tipos de tupla do C#C# tuple types

As tuplas do C# são tipos que você define usando uma sintaxe leve.C# tuples are types that you define using a lightweight syntax. As vantagens incluem sintaxe mais simples, regras para conversões baseadas em números (conhecidas como cardinalidade) e em tipos de elementos, além de regras compatíveis para cópias, testes de igualdade e atribuições.The advantages include a simpler syntax, rules for conversions based on number (referred to as cardinality) and types of elements, and consistent rules for copies, equality tests, and assignments. Em contrapartida, as tuplas não oferecem suporte a algumas das expressões orientadas a objeto associadas à herança.As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance. Você pode obter uma visão geral na seção sobre tuplas no artigo Novidades no C# 7.0.You can get an overview in the section on tuples in the What's new in C# 7.0 article.

Neste artigo, você aprenderá as regras de linguagem que regem as tuplas no C# 7.0 e em versões posteriores, além de diferentes maneiras de usá-las e as diretrizes iniciais para trabalhar com tuplas.In this article, you'll learn the language rules governing tuples in C# 7.0 and later versions, different ways to use them, and initial guidance on working with tuples.

Observação

Os novos recursos de tuplas exigem os tipos ValueTuple.The new tuples features require the ValueTuple types. Você deve adicionar o pacote NuGet System.ValueTuple para usá-lo em plataformas que não incluem os tipos.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

Isso é semelhante a outros recursos de linguagem que dependem de tipos entregues no framework.This is similar to other language features that rely on types delivered in the framework. Os exemplos incluem async e await que dependem da interface INotifyCompletion, além do LINQ que depende de IEnumerable<T>.Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. No entanto, o mecanismo de entrega está mudando conforme o .NET se torna mais independente de plataforma.However, the delivery mechanism is changing as .NET is becoming more platform independent. O .NET Framework pode não ser enviados sempre na mesma cadência que o compilador de linguagem.The .NET Framework may not always ship on the same cadence as the language compiler. Quando novos recursos de linguagem dependerem de novos tipos, esses tipos estarão disponíveis como pacotes do NuGet quando os recursos de linguagem forem enviados.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. Conforme esses novos tipos são adicionados à API padrão do .NET e fornecidos como parte do framework, o requisito de pacote do NuGet será removido.As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

Vamos começar com os motivos para adicionar o novo suporte de tupla.Let's start with the reasons for adding new tuple support. Métodos retornam um único objeto.Methods return a single object. Tuplas permitem que você empacote vários valores nesse único objeto mais facilmente.Tuples enable you to package multiple values in that single object more easily.

O .NET Framework já tem classes Tuple genéricas.The .NET Framework already has generic Tuple classes. Essas classes, no entanto, têm duas limitações importantes.These classes, however, had two major limitations. Por exemplo, as classes Tuple nomearam suas propriedades como Item1, Item2 e assim por diante.For one, the Tuple classes named their properties Item1, Item2, and so on. Esses nomes não carregam informações semânticas.Those names carry no semantic information. O uso desses tipos Tuple não permite comunicar o significado de cada uma das propriedades.Using these Tuple types does not enable communicating the meaning of each of the properties. Os novos recursos de linguagem permitem que você declare e use nomes semanticamente significativos para os elementos em uma tupla.The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

As classes de Tuple causam mais problemas de desempenho porque elas são tipos de referência.The Tuple classes cause more performance concerns because they are reference types. O uso de um dos tipos Tuple significa alocar objetos.Using one of the Tuple types means allocating objects. Em afunilamentos, alocar muitos objetos pequenos pode ter um impacto mensurável no desempenho do aplicativo.On hot paths, allocating many small objects can have a measurable impact on your application's performance. Portanto, o suporte de linguagem para tuplas aproveita os novos structs ValueTuple.Therefore, the language support for tuples leverages the new ValueTuple structs.

Para evitar essas deficiências, você pode criar uma class ou um struct para carregar vários elementos.To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Infelizmente, isso significa mais trabalho para você e obscurece a intenção do design.Unfortunately, that's more work for you, and it obscures your design intent. Fazer uma struct ou class significa que você está definindo um tipo com os dados e comportamento.Making a struct or class implies that you are defining a type with both data and behavior. Muitas vezes, você simplesmente deseja armazenar diversos valores em um único objeto.Many times, you simply want to store multiple values in a single object.

Os recursos de linguagem e os structs genéricos ValueTuple aplicam a regra de que você não pode adicionar nenhum comportamento (método) a esses tipos de tupla.The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. Todos os tipos ValueTuple são structs mutáveis.All the ValueTuple types are mutable structs. Cada campo membro é um campo público.Each member field is a public field. Isso os torna muito simples.That makes them very lightweight. No entanto, isso significa que as tuplas não devem ser usadas quando a imutabilidade é importante.However, that means tuples should not be used where immutability is important.

As tuplas são contêineres de dados mais simples e mais flexíveis do que os tipos class e struct.Tuples are both simpler and more flexible data containers than class and struct types. Vamos explorar essas diferenças.Let's explore those differences.

Tuplas nomeadas e sem nomeNamed and unnamed tuples

O struct ValueTuple tem campos nomeados Item1, Item2, Item3 e assim por diante, semelhante às propriedades definidas nos tipos de Tuple existentes.The ValueTuple struct has fields named Item1, Item2, Item3, and so on, similar to the properties defined in the existing Tuple types. Esses nomes são os únicos nomes que você pode usar para tuplas sem nome.These names are the only names you can use for unnamed tuples. Quando você não fornece nomes de campo alternativos para uma tupla, cria uma tupla sem nome:When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

var unnamed = ("one", "two");

A tupla no exemplo anterior foi inicializada usando constantes literais e não terá nomes de elemento criados usando as projeções de nome de campo de tupla no C# 7.1.The tuple in the previous example was initialized using literal constants and won't have element names created using tuple field name projections in C# 7.1.

No entanto, quando você inicializa uma tupla, pode usar novos recursos de linguagem que fornecem nomes melhores para cada campo.However, when you initialize a tuple, you can use new language features that give better names to each field. Fazer isso cria uma tupla nomeada.Doing so creates a named tuple. As tuplas nomeadas ainda têm elementos chamados Item1, Item2, Item3 e assim por diante.Named tuples still have elements named Item1, Item2, Item3 and so on. Mas também têm sinônimos para qualquer um desses elementos que você tenha nomeado.But they also have synonyms for any of those elements that you have named. Você cria uma tupla nomeada especificando os nomes de cada elemento.You create a named tuple by specifying the names for each element. Uma maneira é especificar os nomes como parte da inicialização da tupla:One way is to specify the names as part of the tuple initialization:

var named = (first: "one", second: "two");

Esses sinônimos são manipulados pelo compilador e pela linguagem para que você possa usar as tuplas nomeadas de forma eficaz.These synonyms are handled by the compiler and the language so that you can use named tuples effectively. Os editores e IDEs podem ler esses nomes semânticos usando APIs Roslyn.IDEs and editors can read these semantic names using the Roslyn APIs. É possível fazer referência aos elementos de uma tupla nomeada com nomes semânticos em qualquer lugar no mesmo assembly.You can reference the elements of a named tuple by those semantic names anywhere in the same assembly. O compilador substitui os nomes que você definiu com equivalentes Item* ao gerar a saída compilada.The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. A MSIL (Microsoft Intermediate Language) compilada não inclui os nomes que você atribuiu a esses elementos.The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

Começando com o C# 7.1, os nomes de campo para uma tupla podem ser fornecidos por meio das variáveis usadas para inicializar a tupla.Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. Isso é conhecido como inicializadores de projeção de tupla .This is referred to as tuple projection initializers. O código a seguir cria uma tupla denominada accumulation com elementos count (um inteiro) e sum (um duplo).The following code creates a tuple named accumulation with elements count (an integer), and sum (a double).

var sum = 12.5;
var count = 5;
var accumulation = (count, sum);

O compilador deve comunicar esses nomes que você criou para tuplas retornadas de métodos públicos ou propriedades.The compiler must communicate those names you created for tuples that are returned from public methods or properties. Nesses casos, o compilador adiciona um atributo TupleElementNamesAttribute no método.In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. Esse atributo contém uma propriedade de lista TransformNames que inclui os nomes dados a cada um desses elementos na tupla.This attribute contains a TransformNames list property that contains the names given to each of the elements in the tuple.

Observação

Ferramentas de desenvolvimento, como o Visual Studio, também leem esses metadados e fornecem IntelliSense e outros recursos usando os nomes de campo de metadados.Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

É importante entender esses conceitos básicos subjacentes das novas tuplas e do tipo ValueTuple para entender as regras para atribuir tuplas nomeadas entre si.It is important to understand these underlying fundamentals of the new tuples and the ValueTuple type in order to understand the rules for assigning named tuples to each other.

Inicializadores de projeção de tuplaTuple projection initializers

Em geral, os inicializadores de projeção de tupla funcionam com o uso dos nomes de campo ou de variáveis do lado direito de uma instrução de inicialização de tupla.In general, tuple projection initializers work by using the variable or field names from the right-hand side of a tuple initialization statement. Se for fornecido um nome explícito, ele terá precedência sobre qualquer nome projetado.If an explicit name is given, that takes precedence over any projected name. Por exemplo, no seguinte inicializador, os elementos são explicitFieldOne e explicitFieldTwo, não localVariableOne e localVariableTwo:For example, in the following initializer, the elements are explicitFieldOne and explicitFieldTwo, not localVariableOne and localVariableTwo:

var localVariableOne = 5;
var localVariableTwo = "some text";

var tuple = (explicitFieldOne: localVariableOne, explicitFieldTwo: localVariableTwo);

Para qualquer campo em que um nome explícito não for fornecido, será projetado um nome implícito aplicável.For any field where an explicit name is not provided, an applicable implicit name is projected. Não há nenhum requisito para fornecer nomes semânticos, explícita ou implicitamente.There is no requirement to provide semantic names, either explicitly or implicitly. O inicializador a seguir terá nomes de campo Item1, cujo valor é 42 e stringContent, cujo valor é "A resposta para tudo":The following initializer has field names Item1, whose value is 42 and stringContent, whose value is "The answer to everything":

var stringContent = "The answer to everything";
var mixedTuple = (42, stringContent);

Há duas condições nas quais os possíveis nomes de campos não são projetados no campo da tupla:There are two conditions where candidate field names are not projected onto the tuple field:

  1. Quando o possível nome é um nome de tupla reservado.When the candidate name is a reserved tuple name. Os exemplos incluem Item3, ToString.Examples include Item3, ToString. ou Rest.or Rest.
  2. Quando o possível nome é uma duplicata de outro nome de campo de tupla, seja explícito ou implícito.When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

Essas condições evitam a ambiguidade.These conditions avoid ambiguity. Esses nomes causariam ambiguidade se fossem usados como nomes de campo em uma tupla.These names would cause an ambiguity if they were used as the field names for a field in a tuple. Nenhuma dessas condições causa erros de tempo de compilação.Neither of these conditions cause compile-time errors. Em vez disso, os elementos sem nomes projetados não terão nomes semânticos projetados para eles.Instead, the elements without projected names do not have semantic names projected for them. Os exemplos a seguir demonstram essas condições:The following examples demonstrate these conditions:

var ToString = "This is some text";
var one = 1;
var Item1 = 5;
var projections = (ToString, one, Item1);
// Accessing the first field:
Console.WriteLine(projections.Item1);
// There is no semantic name 'ToString'
// Accessing the second field:
Console.WriteLine(projections.one);
Console.WriteLine(projections.Item2);
// Accessing the third field:
Console.WriteLine(projections.Item3);
// There is no semantic name 'Item1`.

var pt1 = (X: 3, Y: 0);
var pt2 = (X: 3, Y: 4);

var xCoords = (pt1.X, pt2.X);
// There are no semantic names for the fields
// of xCoords. 

// Accessing the first field:
Console.WriteLine(xCoords.Item1);
// Accessing the second field:
Console.WriteLine(xCoords.Item2);

Essas situações não causam erros de compilador porque essa seria uma alteração significativa nos códigos escritos com C# 7.0, em que as projeções de nome de campo de tupla não estavam disponíveis.These situations do not cause compiler errors because that would be a breaking change for code written with C# 7.0, when tuple field name projections were not available.

Igualdade e tuplasEquality and tuples

Começando com o C# 7.3, os tipos de tupla oferecem suporte aos operadores == e !=.Beginning with C# 7.3, tuple types support the == and != operators. Esses operadores comparam cada membro do argumento da esquerda com cada membro do argumento da direita na ordem.These operators work by comparing each member of the left argument to each member of the right argument in order. Essas comparações são de curto-circuito.These comparisons short-circuit. Elas interromperão a avaliação de membros assim que um par não for igual.They will stop evaluating members as soon as one pair is not equal. O código a seguir exemplifica o uso de ==, mas todas as regras de comparação se aplicam a !=.The following code examples use ==, but the comparison rules all apply to !=. O exemplo de código a seguir mostra uma comparação de igualdade de dois pares de inteiros:The following code example shows an equality comparison for two pairs of integers:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'

Há várias regras que tornam os testes de igualdade de tuplas mais convenientes.There are several rules that make tuple equality tests more convenient. A igualdade de tupla executa conversões lifted se uma das tuplas for uma tupla que permite valor nulo, conforme mostrado no código a seguir:Tuple equality performs lifted conversions if one of the tuples is a nullable tuple, as shown in the following code:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple); // Also true

A igualdade de tupla também executa conversões implícitas em cada membro de ambas as tuplas.Tuple equality also performs implicit conversions on each member of both tuples. Esses incluem conversões lifted, conversões widening ou outras conversões implícitas.These include lifted conversions, widening conversions, or other implicit conversions. Os exemplos a seguir mostram que um inteiro de uma tupla 2 pode ser comparado a um longo de tupla 2 devido à conversão implícita do inteiro para um longo:The following examples show that an integer 2-tuple can be compared to a long 2-tuple because of the implicit conversion from integer to long:

// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true

// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true

// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true

Os nomes dos membros da tupla não participam em testes de igualdade.The names of the tuple members do not participate in tests for equality. No entanto, se um dos operandos for uma tupla literal com nomes explícitos, o compilador gera o aviso CS8383 caso os nomes não correspondam aos nomes do outro operando.However, if one of the operands is a tuple literal with explicit names, the compiler generates warning CS8383 if those names do not match the names of the other operand. No caso em que ambos os operandos são literais de tupla, o aviso é no operando à direita, conforme mostrado no exemplo a seguir:In the case where both operands are tuple literals, the warning is on the right operand as shown in the following example:

(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another); // true. Member names don't participate.
Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names

Por fim, as tuplas podem conter tuplas aninhadas.Finally, tuples may contain nested tuples. A igualdade de tupla compara a "forma" de cada operando por meio de tuplas aninhadas, conforme mostrado no exemplo a seguir:Tuple equality compares the "shape" of each operand through nested tuples as shown in the following example:

(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );

É um erro de tempo de compilação comparar duas tuplas por igualdade (ou desigualdade) quando elas têm formas diferentes.It's a compile time error to compare two tuples for equality (or inequality) when they have different shapes. O compilador não tentará nenhuma desconstrução das tuplas aninhadas para compará-las.The compiler won't attempt any deconstruction of nested tuples in order to compare them.

Atribuição e tuplasAssignment and tuples

A linguagem oferece suporte à atribuição entre tipos de tuplas que têm o mesmo número de elementos, em que cada elemento do lado direito pode ser convertido implicitamente em seu elemento correspondente do lado esquerdo.The language supports assignment between tuple types that have the same number of elements, where each right-hand side element can be implicitly converted to its corresponding left-hand side element. Outras conversões não são consideradas para atribuições.Other conversions aren't considered for assignments. É um erro de tempo de compilação atribuir uma tupla a outra quando elas têm formas diferentes.It's a compile time error to assign one tuple to another when they have different shapes. O compilador não tentará nenhuma desconstrução das tuplas aninhadas para atribuí-las.The compiler won't attempt any deconstruction of nested tuples in order to assign them. Vamos examinar os tipos de atribuições que são permitidos entre tipos de tupla.Let's look at the kinds of assignments that are allowed between tuple types.

Considere estas variáveis usadas nos exemplos a seguir:Consider these variables used in the following examples:

// The 'arity' and 'shape' of all these tuples are compatible. 
// The only difference is the field names being used.
var unnamed = (42, "The meaning of life");
var anonymous = (16, "a perfect square");
var named = (Answer: 42, Message: "The meaning of life");
var differentNamed = (SecretConstant: 42, Label: "The meaning of life");

As primeiras duas variáveis, unnamed e anonymous, não têm nomes semânticos fornecidos para os elementos.The first two variables, unnamed and anonymous do not have semantic names provided for the elements. Os nomes de campo são Item1 e Item2.The field names are Item1 and Item2. As duas últimas variáveis, named e differentName, têm nomes semânticos fornecidos para os elementos.The last two variables, named and differentName have semantic names given for the elements. Estas duas tuplas têm nomes diferentes para os elementos.These two tuples have different names for the elements.

Todas essas quatro tuplas têm o mesmo número de elementos (chamados de "cardinalidade") e os tipos desses elementos são idênticos.All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. Portanto, todas essas atribuições funcionam:Therefore, all of these assignments work:

unnamed = named;

named = unnamed;
// 'named' still has fields that can be referred to
// as 'answer', and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// unnamed to unnamed:
anonymous = unnamed;

// named tuples.
named = differentNamed;
// The field names are not assigned. 'named' still has 
// fields that can be referred to as 'answer' and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// With implicit conversions:
// int can be implicitly converted to long
(long, string) conversion = named;

Observe que os nomes das tuplas não são atribuídos.Notice that the names of the tuples are not assigned. Os valores dos elementos são atribuídos na ordem dos elementos na tupla.The values of the elements are assigned following the order of the elements in the tuple.

As tuplas com tipos ou números de elementos diferentes não são atribuíveis:Tuples of different types or numbers of elements are not assignable:

// Does not compile.
// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
var differentShape = (1, 2, 3);
named = differentShape;

Tuplas como valores retornados do métodoTuples as method return values

Um dos usos mais comuns de tuplas é como um valor retornado do método.One of the most common uses for tuples is as a method return value. Vamos examinar um exemplo.Let's walk through one example. Considere este método que calcula o desvio padrão para uma sequência de números:Consider this method that computes the standard deviation for a sequence of numbers:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    // Step 1: Compute the Mean:
    var mean = sequence.Average();

    // Step 2: Compute the square of the differences between each number 
    // and the mean:
    var squaredMeanDifferences = from n in sequence
                                 select (n - mean) * (n - mean);
    // Step 3: Find the mean of those squared differences:
    var meanOfSquaredDifferences = squaredMeanDifferences.Average();

    // Step 4: Standard Deviation is the square root of that mean:
    var standardDeviation = Math.Sqrt(meanOfSquaredDifferences);
    return standardDeviation;
}

Observação

Esses exemplos calculam o desvio padrão de exemplo não corrigido.These examples compute the uncorrected sample standard deviation. A fórmula do desvio padrão de exemplo corrigida dividiria a soma das diferenças da média ao quadrado por (N-1) em vez de N, como o método de extensão Average faz.The corrected sample standard deviation formula would divide the sum of the squared differences from the mean by (N-1) instead of N, as the Average extension method does. Consulte um texto sobre estatísticas para obter mais detalhes sobre as diferenças entre essas fórmulas para desvio padrão.Consult a statistics text for more details on the differences between these formulas for standard deviation.

O código anterior segue a fórmula típica para o desvio padrão.The preceding code follows the textbook formula for the standard deviation. Ele produz a resposta correta, mas é uma implementação ineficiente.It produces the correct answer, but it's an inefficient implementation. Esse método enumera a sequência duas vezes: uma vez para produzir a média, e uma vez para produzir a média do quadrado da diferença da média.This method enumerates the sequence twice: Once to produce the average, and once to produce the average of the square of the difference of the average. (Lembre-se de que consultas LINQ são avaliadas lentamente, então o cálculo das diferenças da média e a média dessas diferenças compõem apenas uma enumeração.)(Remember that LINQ queries are evaluated lazily, so the computation of the differences from the mean and the average of those differences makes only one enumeration.)

Há uma alternativa fórmula que calcula o desvio padrão usando apenas uma enumeração da sequência.There is an alternative formula that computes standard deviation using only one enumeration of the sequence. Esse cálculo produz dois valores conforme enumera a sequência: a soma de todos os itens na sequência e a soma de cada valor ao quadrado:This computation produces two values as it enumerates the sequence: the sum of all items in the sequence, and the sum of the each value squared:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    double count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Essa versão enumera a sequência exatamente uma vez.This version enumerates the sequence exactly once. Mas não é um código reutilizável.But it's not reusable code. Conforme você continua a trabalhar, descobrirá que muitos cálculos estatísticos diferentes usam o número de itens na sequência, a soma da sequência e a soma dos quadrados da sequência.As you keep working, you'll find that many different statistical computations use the number of items in the sequence, the sum of the sequence, and the sum of the squares of the sequence. Vamos refatorar esse método e escrever um método utilitário que produz todos esses três valores.Let's refactor this method and write a utility method that produces all three of those values. Todos os três valores podem retornar como uma tupla.All three values can be returned as a tuple.

Vamos atualizar esse método para que os três valores calculados durante a enumeração sejam armazenados em uma tupla.Let's update this method so the three values computed during the enumeration are stored in a tuple. Isso cria essa versão:That creates this version:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = (Count: 0, Sum: 0.0, SumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.Count++;
        computation.Sum += item;
        computation.SumOfSquares += item * item;
    }

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

O suporte à refatoração do Visual Studio facilita a extração da funcionalidade para as estatísticas principais em um método privado.Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. Isso fornece a você um método private static que retorna o tipo de tupla com os três valores de Sum, SumOfSquares e Count:That gives you a private static method that returns the tuple type with the three values of Sum, SumOfSquares, and Count:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int Count, double Sum, double SumOfSquares) computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    var computation = (count: 0, sum: 0.0, sumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.count++;
        computation.sum += item;
        computation.sumOfSquares += item * item;
    }

    return computation;
}

A linguagem permite algumas opções adicionais que podem ser usadas se você desejar fazer algumas edições rápidas manualmente.The language enables a couple more options that you can use, if you want to make a few quick edits by hand. Primeiro, você pode usar a declaração var para inicializar o resultado da tupla da chamada do método ComputeSumAndSumOfSquares.First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. Você também pode criar três variáveis discretas dentro do método ComputeSumAndSumOfSquares.You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. A versão final é mostrada no código a seguir:The final version is shown in the following code:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (count, sum, sumOfSquares);
}

Esta versão final pode ser usada para qualquer método que precise desses três valores ou qualquer subconjunto deles.This final version can be used for any method that needs those three values, or any subset of them.

A linguagem dá suporte a outras opções no gerenciamento dos nomes dos elementos nesses métodos de retorno de tupla.The language supports other options in managing the names of the elements in these tuple-returning methods.

Você pode remover os nomes de campo da declaração de valor retornado e retornar uma tupla sem nome:You can remove the field names from the return value declaration and return an unnamed tuple:

private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (sum, sumOfSquares, count);
}

Os campos nesta tupla são nomeados Item1, Item2 e Item3.The fields of this tuple are named Item1, Item2, and Item3. É recomendável que você forneça nomes semânticos para os elementos de tuplas retornados dos métodos.It's recommended that you provide semantic names to the elements of tuples returned from methods.

Outra linguagem em que as tuplas podem ser úteis é quando você estiver criando consultas LINQ.Another idiom where tuples can be useful is when you are authoring LINQ queries. O resultado final projetado geralmente contém algumas, mas não todas, propriedades dos objetos que estão sendo selecionados.The final projected result often contains some, but not all, of the properties of the objects being selected.

Tradicionalmente, você poderia projetar os resultados da consulta em uma sequência de objetos que eram um tipo anônimo.You would traditionally project the results of the query into a sequence of objects that were an anonymous type. Isso apresentava muitas limitações, principalmente porque os tipos anônimos não poderiam ser nomeados de forma conveniente no tipo de retorno de um método.That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. Alternativas usando object ou dynamic como o tipo de resultado acompanham os custos de desempenho significativos.Alternatives using object or dynamic as the type of the result came with significant performance costs.

Retornar uma sequência de um tipo de tupla é fácil e os nomes e tipos desses elementos estão disponíveis no tempo de compilação e por meio de ferramentas de IDE.Returning a sequence of a tuple type is easy, and the names and types of the elements are available at compile time and through IDE tools. Por exemplo, imagine um aplicativo de tarefas.For example, consider a ToDo application. Você pode definir uma classe semelhante à seguinte para representar uma única entrada na lista de tarefas:You might define a class similar to the following to represent a single entry in the ToDo list:

public class ToDoItem
{
    public int ID { get; set; }
    public bool IsDone { get; set; }
    public DateTime DueDate { get; set; }
    public string Title { get; set; }
    public string Notes { get; set; }    
}

Seus aplicativos móveis podem dar suporte a um formato compacto dos itens de tarefas atuais que exibem apenas o título.Your mobile applications may support a compact form of the current ToDo items that only displays the title. Essa consulta LINQ faria uma projeção que inclui somente a ID e o título.That LINQ query would make a projection that includes only the ID and the title. Um método que retorna uma sequência de tuplas expressa bem esse design:A method that returns a sequence of tuples expresses that design well:

internal IEnumerable<(int ID, string Title)> GetCurrentItemsMobileList()
{
    return from item in AllItems
           where !item.IsDone
           orderby item.DueDate
           select (item.ID, item.Title);
}

Observação

No C# 7.1, as projeções de tupla permitem criar tuplas nomeadas usando elementos, de maneira semelhante à nomeação de propriedade nos tipos anônimos.In C# 7.1, tuple projections enable you to create named tuples using elements, in a manner similar to the property naming in anonymous types. No código acima, a instrução select na projeção de consulta cria uma tupla que tem elementos ID e Title.In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

A tupla nomeada pode ser parte da assinatura.The named tuple can be part of the signature. Ela permite que o compilador e as ferramentas de IDE forneçam verificações estáticas de que você está usando o resultado corretamente.It lets the compiler and IDE tools provide static checking that you are using the result correctly. A tupla nomeada também contém as informações de tipo estático, portanto, não há necessidade de usar recursos caros de tempo de execução como reflexão ou associação dinâmica para trabalhar com os resultados.The named tuple also carries the static type information so there is no need to use expensive run time features like reflection or dynamic binding to work with the results.

DesconstruçãoDeconstruction

Você pode descompactar todos os itens em uma tupla desconstruindo a tupla retornada por um método.You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. Há três abordagens diferentes para desconstruir tuplas.There are three different approaches to deconstructing tuples. Primeiro, você pode declarar explicitamente o tipo de cada campo dentro de parênteses para criar variáveis discretas para cada um dos elementos na tupla:First, you can explicitly declare the type of each field inside parentheses to create discrete variables for each of the elements in the tuple:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int count, double sum, double sumOfSquares) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Você também pode declarar variáveis de tipo implícito para cada campo em uma tupla usando a palavra-chave var fora dos parênteses:You can also declare implicitly typed variables for each field in a tuple by using the var keyword outside the parentheses:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var (sum, sumOfSquares, count) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Também é válido usar a palavra-chave var com qualquer uma ou todas as declarações de variável dentro dos parênteses.It is also legal to use the var keyword with any, or all of the variable declarations inside the parentheses.

(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence);

Você não poderá usar um tipo específico fora dos parênteses, mesmo se todos os campos na tupla tiverem o mesmo tipo.You cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

Você também pode desconstruir tuplas com declarações existentes:You can deconstruct tuples with existing declarations as well:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y) => (X, Y) = (x, y);
}

Aviso

Você não pode combinar as declarações existentes com as declarações dentro dos parênteses.You cannot mix existing declarations with declarations inside the parentheses. Por exemplo, o seguinte não é permitido: (var x, y) = MyMethod();.For instance, the following is not allowed: (var x, y) = MyMethod();. Isso gera o erro CS8184 porque x está declarado dentro dos parênteses e y já foi declarado em outro lugar.This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

Desconstruindo tipos definidos pelo usuárioDeconstructing user-defined types

Qualquer tipo de tupla pode ser desconstruído, conforme mostrado acima.Any tuple type can be deconstructed as shown above. Também é fácil habilitar a desconstrução em qualquer tipo definido pelo usuário (classes, structs ou até mesmo interfaces).It's also easy to enable deconstruction on any user-defined type (classes, structs, or even interfaces).

O autor do tipo pode definir um ou mais métodos Deconstruct que atribuem valores a qualquer número de variáveis out que representam os elementos de dados que compõem o tipo.The type author can define one or more Deconstruct methods that assign values to any number of out variables representing the data elements that make up the type. Por exemplo, o tipo Person a seguir define um método Deconstruct que desconstrói um objeto person nos elementos representando o nome e o sobrenome:For example, the following Person type defines a Deconstruct method that deconstructs a person object into the elements representing the first name and last name:

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

O método deconstruct permite a atribuição de um Person para duas cadeias de caracteres, representando as propriedades FirstName e LastName:The deconstruct method enables assignment from a Person to two strings, representing the FirstName and LastName properties:

var p = new Person("Althea", "Goodwin");
var (first, last) = p;

Você pode habilitar a desconstrução mesmo para os tipos que você não criou.You can enable deconstruction even for types you did not author. O método Deconstruct pode ser um método de extensão que retira do pacote os membros de dados acessíveis de um objeto.The Deconstruct method can be an extension method that unpackages the accessible data members of an object. O exemplo a seguir mostra um tipo Student, derivado do tipo Person e um método de extensão que desconstrói um Student em três variáveis, representando a FirstName, a LastName e a GPA:The example below shows a Student type, derived from the Person type, and an extension method that deconstructs a Student into three variables, representing the FirstName, the LastName, and the GPA:

public class Student : Person
{
    public double GPA { get; }
    public Student(string first, string last, double gpa) :
        base(first, last)
    {
        GPA = gpa;
    }
}

public static class Extensions
{
    public static void Deconstruct(this Student s, out string first, out string last, out double gpa)
    {
        first = s.FirstName;
        last = s.LastName;
        gpa = s.GPA;
    }
}

Um objeto Student agora tem dois métodos Deconstruct acessíveis: o método de extensão declarado para tipos Student e o membro do tipo Person.A Student object now has two accessible Deconstruct methods: the extension method declared for Student types, and the member of the Person type. Ambos estão no escopo e isso permite que um Student seja desconstruído em duas ou três variáveis.Both are in scope, and that enables a Student to be deconstructed into either two variables or three. Se você atribuir um aluno a três variáveis, o nome, o sobrenome e a GPA serão retornados.If you assign a student to three variables, the first name, last name, and GPA are all returned. Se você atribuir um aluno a duas variáveis, apenas o nome e o sobrenome serão retornados.If you assign a student to two variables, only the first name and the last name are returned.

var s1 = new Student("Cary", "Totten", 4.5);
var (fName, lName, gpa) = s1;

Você deve tomar cuidado ao definir vários métodos Deconstruct em uma classe ou hierarquia de classes.You should be careful defining multiple Deconstruct methods in a class or a class hierarchy. Vários métodos Deconstruct que têm o mesmo número de parâmetros out podem causar ambiguidades rapidamente.Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. Os chamadores podem não conseguir chamar facilmente o método Deconstruct desejado.Callers may not be able to easily call the desired Deconstruct method.

Neste exemplo, há uma chance mínima de uma chamada ambígua porque o método Deconstruct para Person tem dois parâmetros de saída e o método Deconstruct para Student tem três.In this example, there is minimal chance for an ambiguous call because the Deconstruct method for Person has two output parameters, and the Deconstruct method for Student has three.

Os operadores de desconstrução não participam do teste de igualdade.Deconstruction operators do not participate in testing equality. O exemplo a seguir gera o erro do compilador CS0019:The following example generates compiler error CS0019:

Person p = new Person("Althea", "Goodwin");
if (("Althea", "Goodwin") == p)
    Console.WriteLine(p);

O método Deconstruct pôde converter o objetoPerson p em uma tupla que contém duas cadeias de caracteres, mas não é aplicável no contexto de testes de igualdade.The Deconstruct method could convert the Person object p to a tuple containing two strings, but it is not applicable in the context of equality tests.

ConclusãoConclusion

O novo suporte de linguagem e biblioteca para tuplas nomeadas torna muito mais fácil trabalhar com designs que usam estruturas de dados que armazenam vários elementos, mas não definem comportamento, como as classes e os structs fazem.The new language and library support for named tuples makes it much easier to work with designs that use data structures that store multiple elements but do not define behavior, as classes and structs do. É fácil e sucinto usar tuplas para esses tipos.It's easy and concise to use tuples for those types. Você obtém todos os benefícios da verificação de tipo estático, sem precisar criar tipos usando a sintaxe de class ou de struct mais detalhada.You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. Mesmo assim, elas são mais úteis para métodos utilitários que são private ou internal.Even so, they are most useful for utility methods that are private, or internal. Cria tipos definidos pelo usuário, tipos class ou struct, quando seus métodos públicos retornam um valor que tem vários elementos.Create user-defined types, either class or struct types when your public methods return a value that has multiple elements.