Tipos de tupla de C#C# tuple types

Las tuplas de C# son tipos que se definen mediante una sintaxis ligera.C# tuples are types that you define using a lightweight syntax. Entre otras ventajas, incluyen una sintaxis más sencilla, reglas para conversiones en función de un número (denominadas cardinalidad) y tipos de elementos y reglas coherentes para copias, pruebas de igualdad y asignaciones.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. Como contrapartida, las tuplas no admiten algunas de las expresiones orientadas a objetos que se asocian a la herencia.As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance. Puede obtener información general en la sección sobre tuplas del artículo Novedades de C# 7.0.You can get an overview in the section on tuples in the What's new in C# 7.0 article.

En este artículo conocerá las reglas del lenguaje que rigen las tuplas en C# 7.0 y versiones posteriores, distintas formas de usarlas y una guía inicial sobre cómo trabajar con 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.

Nota

Las nuevas características de tupla requieren los tipos ValueTuple.The new tuples features require the ValueTuple types. Debe agregar el paquete NuGet System.ValueTuple para usarlo en plataformas que no incluyen los tipos.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

Esto es similar a otras características del lenguaje que se basan en tipos que se han proporcionado en el marco.This is similar to other language features that rely on types delivered in the framework. Algunos ejemplos incluyen async y await que se basan en la interfaz INotifyCompletion, y LINQ que se basa en IEnumerable<T>.Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. En cambio, el mecanismo de entrega está cambiando a medida que .NET está pasando a ser más independiente de las plataformas.However, the delivery mechanism is changing as .NET is becoming more platform independent. Puede que .NET Framework no proporcione siempre la misma cadencia que el compilador de lenguaje.The .NET Framework may not always ship on the same cadence as the language compiler. Cuando las nuevas características del lenguaje se basan en tipos nuevos, esos tipos estarán disponibles como paquetes NuGet cuando se proporcionen las características del lenguaje.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. Como estos tipos nuevos se agregan a la API de .NET Standard y se proporcionan como parte del marco, el requisito del paquete NuGet se quitará.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.

Empecemos por las razones para agregar nueva compatibilidad de tupla.Let's start with the reasons for adding new tuple support. Los métodos devuelven un solo objeto.Methods return a single object. Las tuplas permiten empaquetar varios valores en ese único objeto más fácilmente.Tuples enable you to package multiple values in that single object more easily.

.NET Framework ya incluye clases Tuple genéricas.The .NET Framework already has generic Tuple classes. Pero estas clases planteaban dos importantes limitaciones.These classes, however, had two major limitations. En primer lugar, las clases Tuple denominan a sus propiedades Item1, Item2, etc.For one, the Tuple classes named their properties Item1, Item2, and so on. Esos nombres no incluyen ninguna información semántica.Those names carry no semantic information. El uso de estos tipos Tuple no permite comunicar el significado de cada una de las propiedades.Using these Tuple types does not enable communicating the meaning of each of the properties. Las nuevas características de lenguaje permiten declarar y utilizar nombres semánticamente significativos para los elementos de una tupla.The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

Las clases Tuple provocan más problemas de rendimiento porque son tipos de referencia.The Tuple classes cause more performance concerns because they are reference types. El uso de uno de los tipos Tuple implica la asignación de objetos.Using one of the Tuple types means allocating objects. En rutas de acceso activas, la asignación de muchos objetos pequeños puede suponer un importante impacto en el rendimiento de la aplicación.On hot paths, allocating many small objects can have a measurable impact on your application's performance. Por lo tanto, la compatibilidad del lenguaje para tuplas aprovecha los nuevos struct ValueTuple.Therefore, the language support for tuples leverages the new ValueTuple structs.

Para evitar esas deficiencias, podría crear un class o struct que incluya varios elementos.To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Lamentablemente, esto representa más trabajo para el usuario y oculta la intención del diseño.Unfortunately, that's more work for you, and it obscures your design intent. La creación de un struct o class implica que se define un tipo con datos y comportamiento.Making a struct or class implies that you are defining a type with both data and behavior. Muchas veces, simplemente quiere almacenar varios valores en un solo objeto.Many times, you simply want to store multiple values in a single object.

Las características del lenguaje y los structs genéricos ValueTuple aplican la regla que establece que no se puede agregar ningún comportamiento (métodos) a estos 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 los tipos ValueTuple son structs mutables.All the ValueTuple types are mutable structs. Cada campo de miembro es un campo público.Each member field is a public field. Eso los hace muy ligeros.That makes them very lightweight. Pero indica que no se deben usar tuplas cuando la inmutabilidad es importante.However, that means tuples should not be used where immutability is important.

Las tuplas son contenedores de datos más sencillos y flexibles que los tipos class y struct.Tuples are both simpler and more flexible data containers than class and struct types. Examinemos esas diferencias.Let's explore those differences.

Tuplas con nombre y sin nombreNamed and unnamed tuples

El struct ValueTuple incluye campos denominados Item1, Item2, Item3, etc., similares a las propiedades definidas en los tipos Tuple existentes.The ValueTuple struct has fields named Item1, Item2, Item3, and so on, similar to the properties defined in the existing Tuple types. Estos nombres son los únicos que se pueden usar en tuplas sin nombre.These names are the only names you can use for unnamed tuples. Si no proporciona ningún nombre de campo alternativo para una tupla, ha creado una tupla sin nombre:When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

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

La tupla del ejemplo anterior se ha inicializado con constantes literales y no tienen nombres de elementos creados mediante proyecciones de nombre de campo de tupla en 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.

Pero al inicializar una tupla, se pueden usar nuevas características del lenguaje que asignen nombres mejores a cada campo.However, when you initialize a tuple, you can use new language features that give better names to each field. Así se crea una tupla con nombre.Doing so creates a named tuple. Las tuplas con nombre todavía tienen elementos denominados Item1, Item2, Item3, etc.Named tuples still have elements named Item1, Item2, Item3 and so on. Pero también tienen sinónimos para cualquiera de esos elementos a los que haya asignado un nombre.But they also have synonyms for any of those elements that you have named. Cree una tupla con nombre especificando los nombres de cada elemento.You create a named tuple by specifying the names for each element. Una forma consiste en especificar los nombres como parte de la inicialización de la tupla:One way is to specify the names as part of the tuple initialization:

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

Estos sinónimos los controlan el compilador y el lenguaje para que se puedan usar tuplas con nombre de manera eficaz.These synonyms are handled by the compiler and the language so that you can use named tuples effectively. Los IDE y los editores pueden leer estos nombres semánticos con las API de Roslyn.IDEs and editors can read these semantic names using the Roslyn APIs. Puede hacer referencia a los elementos de una tupla con nombre por esos nombres semánticos en cualquier lugar de un mismo ensamblado.You can reference the elements of a named tuple by those semantic names anywhere in the same assembly. El compilador reemplaza los nombres que ha definido con Item* equivalentes al generar la salida compilada.The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. El Lenguaje Intermedio de Microsoft (MSIL) compilado no incluye los nombres que se hayan asignado a estos elementos.The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

A partir de C# 7.1, se pueden proporcionar nombres de campo a una tupla desde las variables que se utilizan para inicializar la tupla.Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. Esto se conoce como inicializadores de proyección de tupla.This is referred to as tuple projection initializers. El código siguiente crea una tupla denominada accumulation con elementos count (un entero) y sum (un doble).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);

El compilador debe comunicar esos nombres creados por el usuario para tuplas que se devuelvan de métodos o propiedades públicos.The compiler must communicate those names you created for tuples that are returned from public methods or properties. En esos casos, el compilador agrega un atributo TupleElementNamesAttribute en el método.In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. Este atributo contiene una propiedad de lista TransformNames que contiene los nombres asignados a cada uno de los elementos de la tupla.This attribute contains a TransformNames list property that contains the names given to each of the elements in the tuple.

Nota

Las herramientas de desarrollo, como Visual Studio, también leen esos metadatos y proporcionan IntelliSense y otras características con los nombres de campo de metadatos.Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

Es importante comprender estos aspectos fundamentales subyacentes de las nuevas tuplas y el tipo ValueTuple para entender las reglas de asignación de tuplas con nombre entre sí.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 proyección de tuplaTuple projection initializers

En general, los inicializadores de proyección de tupla funcionan con los nombres de variable o de campo desde el lado derecho de una instrucción de inicialización 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. Si no se proporciona un nombre explícito, tiene prioridad sobre cualquier nombre proyectado.If an explicit name is given, that takes precedence over any projected name. Por ejemplo, en el inicializador siguiente, los elementos son explicitFieldOne y explicitFieldTwo, no localVariableOne y 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 cualquier campo en el que no se proporcione un nombre explícito, se proyecta un nombre implícito aplicable.For any field where an explicit name is not provided, an applicable implicit name is projected. No hay ningún requisito para proporcionar nombres semánticos, ya sea explícita o implícitamente.There is no requirement to provide semantic names, either explicitly or implicitly. El inicializador siguiente tiene nombres de campo Item1, cuyo valor es 42 y stringContent, cuyo valor es "The answer to everything":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);

Hay dos condiciones donde los nombres de campo de candidato no se proyectan en el campo de la tupla:There are two conditions where candidate field names are not projected onto the tuple field:

  1. Cuando el nombre del candidato es un nombre de tupla reservado.When the candidate name is a reserved tuple name. Entre los ejemplos se incluyen Item3, ToStringExamples include Item3, ToString. o Rest.or Rest.
  2. Cuando el nombre del candidato es un duplicado de otro nombre de campo de tupla, ya sea explícita o implícita.When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

Estas condiciones evitan la ambigüedad.These conditions avoid ambiguity. Estos nombres podrían causar una ambigüedad si se usan como nombres de campo para un campo de una tupla.These names would cause an ambiguity if they were used as the field names for a field in a tuple. Ninguna de estas condiciones causan errores en tiempo de compilación.Neither of these conditions cause compile-time errors. En su lugar, los elementos sin nombres proyectados no tienen nombres semánticos proyectados para ellos.Instead, the elements without projected names do not have semantic names projected for them. Los ejemplos siguientes explican estas condiciones: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);

Estas situaciones no causan errores de compilador dado que sería un cambio importante para el código escrito con C# 7.0, cuando las proyecciones del nombre de campo de tupla no estaban disponibles.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.

Igualdad y tuplasEquality and tuples

Comenzando con C# 7.3, los tipos de tupla admiten los operadores == y !=.Beginning with C# 7.3, tuple types support the == and != operators. Estos operadores funcionan comparando cada uno de los miembros del argumento izquierdo con los miembros del argumento derecho en orden.These operators work by comparing each member of the left argument to each member of the right argument in order. Estas comparaciones cortocircuitan.These comparisons short-circuit. El operador == deja de evaluar a los miembros en cuanto un par no es igual.The == operator stops evaluating members as soon as one pair is not equal. El operador != deja de evaluar a los miembros en cuanto un par es igual.The != operator stops evaluating members as soon as one pair is equal. Los siguientes ejemplos de código usan ==, pero todas las reglas de comparación se aplican a !=.The following code examples use ==, but the comparison rules all apply to !=. En el siguiente ejemplo de código se muestra una comparación de igualdad para dos pares de enteros: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'

Hay varias reglas que hacen que las pruebas de igualdad de tupla sean más prácticas.There are several rules that make tuple equality tests more convenient. La igualdad de tupla realiza conversiones elevadas si una de las tuplas es una tupla que admite valores NULL, como se muestra en el siguiente código: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

La igualdad de tupla también realiza conversiones implícitas en cada uno de los miembros de ambas tuplas.Tuple equality also performs implicit conversions on each member of both tuples. Entre estas se incluyen conversiones elevadas, conversiones de ampliación u otras conversiones implícitas.These include lifted conversions, widening conversions, or other implicit conversions. En los siguientes ejemplos se muestra que una tupla de 2 entera se puede comparar con una tupla de 2 larga debido a la conversión implícita de entero a largo: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

Los nombres de los miembros de las tuplas no participan en las pruebas para la igualdad.The names of the tuple members do not participate in tests for equality. Sin embargo, si uno de los operandos es un literal de tupla con nombres explícitos, el compilador genera la advertencia CS8383 si esos nombres no coinciden con los nombres del otro 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. Cuando ambos operandos son literales de tupla, la advertencia está en el operando derecho como se muestra en el siguiente ejemplo: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 último, las tuplas pueden contener tuplas anidadas.Finally, tuples may contain nested tuples. La igualdad de tupla compara la "forma" de cada operando a través de tuplas anidadas como se muestra en el siguiente ejemplo: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)) );

Asignación y tuplasAssignment and tuples

El lenguaje admite la asignación entre tipos de tupla que tienen el mismo número de elementos, donde cada elemento del lado derecho se puede convertir de forma implícita en su elemento del lado izquierdo correspondiente.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. Otras conversiones no se tienen en cuenta para las asignaciones.Other conversions are not considered for assignments. Echemos un vistazo a los tipos de asignaciones que se permiten entre los tipos de tupla.Let's look at the kinds of assignments that are allowed between tuple types.

Tenga en cuenta estas variables que se usan en los ejemplos siguientes: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");

Las dos primeras variables, unnamed y anonymous, no tienen nombres semánticos proporcionados para los elementos.The first two variables, unnamed and anonymous do not have semantic names provided for the elements. Los nombres de campo son Item1 y Item2.The field names are Item1 and Item2. Las dos últimas variables, named y differentName, tienen nombres semánticos asignados a los elementos.The last two variables, named and differentName have semantic names given for the elements. Estas dos tuplas tienen nombres diferentes para los elementos.These two tuples have different names for the elements.

Estas cuatro tuplas tienen el mismo número de elementos (denominados "cardinalidad") y los tipos de esos elementos son 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. Por consiguiente, todas estas asignaciones funcionan: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 los nombres de las tuplas no se asignan.Notice that the names of the tuples are not assigned. Los valores de los elementos se asignan según el orden de los campos de la tupla.The values of the elements are assigned following the order of the elements in the tuple.

Las tuplas de diferentes tipos o números de elementos no son asignables: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 devueltos del métodoTuples as method return values

Uno de los usos más comunes de tuplas es como un valor devuelto del método.One of the most common uses for tuples is as a method return value. Examinemos un ejemplo.Let's walk through one example. Tenga en cuenta este método que calcula la desviación estándar para una secuencia 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;
}

Nota

En estos ejemplos se calcula la desviación estándar de muestra sin corregir.These examples compute the uncorrected sample standard deviation. La fórmula de desviación estándar de muestra corregida dividiría la suma de las diferencias al cuadrado de la media por (N-1) en lugar de N, como hace el método de extensión Average.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. Para obtener más detalles sobre las diferencias entre estas fórmulas de desviación estándar, consulte un texto de estadísticas.Consult a statistics text for more details on the differences between these formulas for standard deviation.

El código anterior sigue la fórmula clásica para la desviación estándar.The preceding code follows the textbook formula for the standard deviation. Genera la respuesta correcta, pero es una implementación poco eficaz.It produces the correct answer, but it's an inefficient implementation. Este método enumera la secuencia dos veces: una para generar el promedio y otra para generar el promedio del cuadrado de la diferencia del promedio.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. (Recuerde que las consultas LINQ se evalúan de forma diferida, por lo que el cálculo de las diferencias de la media y el promedio de estas diferencias crea una sola enumeración).(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.)

Hay una fórmula alternativa que calcula la desviación estándar mediante una sola enumeración de la secuencia.There is an alternative formula that computes standard deviation using only one enumeration of the sequence. Este cálculo genera dos valores cuando enumera la secuencia: la suma de todos los elementos de la secuencia y la suma del valor de cada cuadrado: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);
}

Esta versión enumera la secuencia exactamente una vez.This version enumerates the sequence exactly once. Pero no se trata de código reutilizable.But it's not reusable code. A medida que siga trabajando, verá que muchos cálculos estadísticos diferentes usan el número de elementos de la secuencia, la suma de la secuencia y la suma de los cuadrados de la secuencia.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 a refactorizar este método y escribir un método de utilidad que genera esos tres valores.Let's refactor this method and write a utility method that produces all three of those values. Los tres valores pueden devolverse como tupla.All three values can be returned as a tuple.

Vamos a actualizar este método para que los tres valores calculados durante la enumeración se almacenen en una tupla.Let's update this method so the three values computed during the enumeration are stored in a tuple. Se crea esta versión: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);
}

La compatibilidad de refactorización de Visual Studio facilita la extracción de la funcionalidad de las estadísticas principales en un método privado.Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. Le ofrece un método private static que devuelve el tipo de tupla con los tres valores de Sum, SumOfSquares y 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 = ComputeSumsAnSumOfSquares(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) ComputeSumsAnSumOfSquares(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;
}

El lenguaje permite un par de opciones más que puede usar si quiere realizar algunas modificaciones 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. En primer lugar, puede usar la declaración var para inicializar el resultado de la tupla de la llamada al método ComputeSumAndSumOfSquares.First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. También puede crear tres variables discretas dentro del método ComputeSumAndSumOfSquares.You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. La versión final se muestra en el siguiente código: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 versión final puede usarse en cualquier método que necesite esas tres variables o cualquier subconjunto de ellas.This final version can be used for any method that needs those three values, or any subset of them.

El lenguaje admite otras opciones de administración de los nombres de los elementos en estos métodos de devolución de tuplas.The language supports other options in managing the names of the elements in these tuple-returning methods.

Puede quitar los nombres de campo de la declaración de valor devuelto y devolver una tupla sin nombre: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);
}

Los campos de esta tupla se denominan Item1, Item2 y Item3.The fields of this tuple are named Item1, Item2, and Item3. Se recomienda proporcionar nombres semánticos a los elementos de tuplas devueltas por los métodos.It's recommended that you provide semantic names to the elements of tuples returned from methods.

Otra expresión donde las tuplas pueden ser útiles es cuando crea consultas LINQ.Another idiom where tuples can be useful is when you are authoring LINQ queries. El resultado proyectado final suele contener algunas de las propiedades de los objetos que se seleccionan, pero no todas.The final projected result often contains some, but not all, of the properties of the objects being selected.

Tradicionalmente, los resultados de la consulta se proyectarían en una secuencia de objetos que fueran un tipo anónimo.You would traditionally project the results of the query into a sequence of objects that were an anonymous type. Eso suponía muchas limitaciones, sobre todo porque a los tipos anónimos no se les podía asignar nombre cómodamente en el tipo de valor devuelto para un método.That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. Las alternativas que usaban object o dynamic como tipo de resultado acarreaban importantes costos de rendimiento.Alternatives using object or dynamic as the type of the result came with significant performance costs.

Devolver una secuencia de tipo de tupla es fácil, y los nombres y tipos de los elementos están disponibles en tiempo de compilación y a través de herramientas 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 ejemplo, consideremos una aplicación de tareas pendientes.For example, consider a ToDo application. Puede definir una clase similar a la siguiente para representar una sola entrada de la lista de tareas pendientes: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; }    
}

Puede que las aplicaciones móviles admitan una forma compacta de las tareas pendientes actuales que solo muestra el título.Your mobile applications may support a compact form of the current ToDo items that only displays the title. Esa consulta LINQ haría una proyección que solo incluya el identificador y el título.That LINQ query would make a projection that includes only the ID and the title. Un método que devuelve una secuencia de tuplas expresa bien ese diseño: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);
}

Nota

En C# 7.1, las proyecciones de tupla permiten crear tuplas con nombre mediante elementos, de forma similar a la propiedad en 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. En el código anterior, la instrucción select de la proyección de consultas crea una tupla con los elementos ID y Title.In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

La tupla con nombre puede ser parte de la firma.The named tuple can be part of the signature. Permite que el compilador y las herramientas de IDE usen comprobación de tipo estáticos para ver que el resultado se usa correctamente.It lets the compiler and IDE tools provide static checking that you are using the result correctly. La tupla con nombre también incluye información de tipos estáticos, por lo que no hay que usar costosas características en tiempo de ejecución tales como la reflexión o los enlaces dinámicos para trabajar con los 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.

DeconstrucciónDeconstruction

Puede desempaquetar todos los elementos de una tupla deconstruyendo la tupla devuelta por un método.You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. Existen tres enfoques diferentes para deconstruir tuplas.There are three different approaches to deconstructing tuples. En primer lugar, se puede declarar explícitamente el tipo de cada campo entre paréntesis para crear variables discretas para cada uno de los elementos de la 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);
}

También puede declarar variables con tipo implícito para cada campo de una tupla mediante la palabra clave var fuera de los paréntesis: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);
}

También es válido usar la palabra clave var con alguna de las declaraciones de variable, o todas, dentro de los paréntesis.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);

No se puede usar un tipo específico fuera de los paréntesis, aunque todos los campos de la tupla tengan el mismo tipo.You cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

También puede deconstruir tuplas con declaraciones 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);
}

Advertencia

No se pueden mezclar declaraciones existentes con declaraciones incluidas en paréntesis.You cannot mix existing declarations with declarations inside the parentheses. Por ejemplo, no se permite lo siguiente: (var x, y) = MyMethod();.For instance, the following is not allowed: (var x, y) = MyMethod();. Esto produce el error CS8184 porque x se declara dentro de los paréntesis e y se ha declarado previamente en otro lugar.This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

Deconstruir tipos definidos por el usuarioDeconstructing user-defined types

Cualquier tipo de tupla puede deconstruirse, tal y como se muestra anteriormente.Any tuple type can be deconstructed as shown above. También resulta fácil habilitar la deconstrucción en cualquier tipo definido por el usuario (clases, structs o incluso interfaces).It's also easy to enable deconstruction on any user-defined type (classes, structs, or even interfaces).

El autor del tipo puede definir uno o varios métodos Deconstruct que asignen valores a cualquier número de variables out que representen los elementos de datos que componen el 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 ejemplo, el tipo Person siguiente define un método Deconstruct que deconstruye un objeto person en los elementos que representan el nombre y apellido: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;
    }
}

El método deconstruct permite la asignación desde un objeto Person en dos cadenas que representan las propiedades FirstName y 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;

Puede habilitar la deconstrucción incluso para tipos que no ha creado.You can enable deconstruction even for types you did not author. El método Deconstruct puede ser un método de extensión que desempaqueta los miembros de datos accesibles de un objeto.The Deconstruct method can be an extension method that unpackages the accessible data members of an object. En el ejemplo siguiente se muestra un tipo Student, derivado del tipo Person y un método de extensión que deconstruye un Student en tres variables, que representan el FirstName, el LastName y 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;
    }
}

Un objeto Student ahora tiene dos métodos Deconstruct accesibles: el método de extensión declarado para tipos Student y el miembro del 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án en el ámbito, lo que permite que un Student se deconstruya en dos variables o tres.Both are in scope, and that enables a Student to be deconstructed into either two variables or three. Si asigna a un alumno tres variables, se devuelven todas, el nombre, el apellido y el GPA.If you assign a student to three variables, the first name, last name, and GPA are all returned. Si asigna a un alumno dos variables, solo se devuelven el nombre y el apellido.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;

Se debe tener cuidado al definir varios métodos Deconstruct en una clase o una jerarquía de clases.You should be careful defining multiple Deconstruct methods in a class or a class hierarchy. Varios métodos Deconstruct con el mismo número de parámetros out pueden generar ambigüedades rápidamente.Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. Puede que los autores de llamadas no puedan llamar fácilmente al método Deconstruct que quieran.Callers may not be able to easily call the desired Deconstruct method.

En este ejemplo, la posibilidad de una llamada ambigua es mínima porque el método Deconstruct para Person tiene dos parámetros de salida y el método Deconstruct para Student tiene tres.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.

Los operadores de deconstrucción no participan en la igualdad de las pruebas.Deconstruction operators do not participate in testing equality. El ejemplo siguiente genera el error del compilador CS0019:The following example generates compiler error CS0019:

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

El método Deconstruct podría convertir el objeto Person p en una tupla que contiene dos cadenas, pero no se puede aplicar en el contexto de las pruebas de igualdad.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.

ConclusiónConclusion

La compatibilidad con tuplas con nombre del nuevo lenguaje y la biblioteca hace que resulte mucho más fácil trabajar con diseños que usan estructuras de datos que almacenan varios elementos, pero no definen el comportamiento, como clases y structs.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. El uso de tuplas para esos tipos resulta fácil y conciso.It's easy and concise to use tuples for those types. Se tienen todas las ventajas de la comprobación de tipos estáticos, sin necesidad de crear tipos con la sintaxis más detallada de class o struct.You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. Aun así, resultan muy útiles para métodos de utilidad que sean private o internal.Even so, they are most useful for utility methods that are private, or internal. Cree tipos definidos por el usuario, tipos class o struct, cuando los métodos públicos devuelvan un valor con varios elementos.Create user-defined types, either class or struct types when your public methods return a value that has multiple elements.