Tipi tupla in C#C# tuple types

Le tuple in C# sono tipi definiti tramite una sintassi leggera.C# tuples are types that you define using a lightweight syntax. I vantaggi includono una sintassi più semplice, regole per le conversioni basate sul numero (note come cardinalità) e sui tipi di elementi, nonché regole coerenti per copie, test di uguaglianza e assegnazioni.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. Lo svantaggio è che le tuple non supportano alcuni dei meccanismi orientati agli oggetti associati all'ereditarietà.As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance. È possibile ottenere una panoramica nella sezione dedicata alle tuple nell'articolo Novità di C# 7.0.You can get an overview in the section on tuples in the What's new in C# 7.0 article.

Questo articolo descrive le regole del linguaggio che controllano le tuple in C# 7.0 e versioni successive, i diversi modi per usarle e linee guida iniziali per l'utilizzo delle tuple.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

Le nuove funzionalità delle tuple richiedono i tipi ValueTuple.The new tuples features require the ValueTuple types. È necessario aggiungere il pacchetto NuGet System.ValueTuple per usarlo nelle piattaforme che non includono i tipi.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

È simile ad altre funzionalità del linguaggio basate sui tipi resi disponibili nel framework.This is similar to other language features that rely on types delivered in the framework. Alcuni esempi sono async e await, basati sull'interfaccia INotifyCompletion, e LINQ, basato su IEnumerable<T>.Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. Tuttavia, il meccanismo di distribuzione sta cambiando perché .NET sta diventando più indipendente dalla piattaforma.However, the delivery mechanism is changing as .NET is becoming more platform independent. .NET Framework potrebbe non essere sempre distribuito secondo la stessa cadenza del compilatore del linguaggio.The .NET Framework may not always ship on the same cadence as the language compiler. Quando nuove funzionalità del linguaggio si basano su nuovi tipi, tali tipi saranno disponibili come pacchetti NuGet al momento della distribuzione delle funzionalità del linguaggio.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. Una volta che questi nuovi tipi vengono aggiunti all'API standard .NET e distribuiti come parte del framework, il requisito del pacchetto NuGet viene rimosso.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.

Per iniziare, verranno spiegati i motivi dell'aggiunta del nuovo supporto per le tuple.Let's start with the reasons for adding new tuple support. I metodi restituiscono un oggetto singolo.Methods return a single object. Le tuple consentono di creare più facilmente pacchetti di valori multipli in questo oggetto singolo.Tuples enable you to package multiple values in that single object more easily.

.NET Framework dispone già di classi Tuple generiche.The .NET Framework already has generic Tuple classes. Queste classi, tuttavia, presentano due limitazioni principali.These classes, however, had two major limitations. Da un lato, le classi Tuple hanno denominato le proprietà Item1, Item2 e così via.For one, the Tuple classes named their properties Item1, Item2, and so on. Questi nomi non includono informazioni semantiche.Those names carry no semantic information. L'uso di questi tipi Tuple non consente di comunicare il significato di ciascuna proprietà.Using these Tuple types does not enable communicating the meaning of each of the properties. Le nuove funzionalità del linguaggio consentono di dichiarare e utilizzare nomi significativi semanticamente per gli elementi in una tupla.The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

Le classi Tuple causano più problemi di prestazioni perché sono tipi riferimento.The Tuple classes cause more performance concerns because they are reference types. L'uso di uno dei tipi Tuple si traduce nell'allocazione di oggetti.Using one of the Tuple types means allocating objects. Nei percorsi critici, l'allocazione di molti oggetti piccoli può avere un impatto notevole sulle prestazioni dell'applicazione.On hot paths, allocating many small objects can have a measurable impact on your application's performance. Pertanto, il supporto del linguaggio per le tuple sfrutta i nuovi struct ValueTuple.Therefore, the language support for tuples leverages the new ValueTuple structs.

Per evitare tali problematiche, è possibile creare un class o struct per supportare più elementi.To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Sfortunatamente, ciò comporta più lavoro per l'utente e rende poco chiare le finalità.Unfortunately, that's more work for you, and it obscures your design intent. Compilare uno struct o una class implica la definizione di un tipo con dati e comportamento.Making a struct or class implies that you are defining a type with both data and behavior. In molti casi, si desidera semplicemente archiviare più valori in un singolo oggetto.Many times, you simply want to store multiple values in a single object.

Le funzionalità del linguaggio e gli struct ValueTuple generici applicano la regola che impedisce all'utente di aggiungere comportamenti (metodi) a questi tipi di tupla.The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. Tutti i tipi ValueTuple sono struct modificabili.All the ValueTuple types are mutable structs. Ogni campo del membro è un campo pubblico.Each member field is a public field. Questo rende i campi molto leggeri.That makes them very lightweight. Tuttavia, ciò implica che le tuple non devono essere usate nei contesti in cui l'immutabilità è un fattore importante.However, that means tuples should not be used where immutability is important.

Le tuple sono contenitori di dati più semplici e flessibili rispetto ai tipi class e struct.Tuples are both simpler and more flexible data containers than class and struct types. Esaminiamo queste differenze.Let's explore those differences.

Tuple con e senza nomeNamed and unnamed tuples

Lo struct ValueTuple include campi denominati Item1, Item2, Item3 e così via, simili alle proprietà definite nei tipi Tuple esistenti.The ValueTuple struct has fields named Item1, Item2, Item3, and so on, similar to the properties defined in the existing Tuple types. Questi nomi sono gli unici nomi che è possibile usare per le tuple senza nome.These names are the only names you can use for unnamed tuples. Quando non si forniscono alla tupla nomi alternativi per i campi, viene creata una tupla senza nome:When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

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

La tupla nell'esempio precedente è stata inizializzata tramite costanti letterali e non avrà nomi di elemento creati con le proiezioni dei nomi di campo di tupla in 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.

Tuttavia, quando si inizializza una tupla, è possibile usare nuove funzionalità del linguaggio che assegnano a ciascun campo un nome più semplice.However, when you initialize a tuple, you can use new language features that give better names to each field. In questo modo viene creata una tupla con nome.Doing so creates a named tuple. Le tuple con nome dispongono ancora di elementi denominati Item1, Item2, Item3 e così via.Named tuples still have elements named Item1, Item2, Item3 and so on. Hanno tuttavia anche sinonimi per eventuali elementi a cui l'utente ha assegnato un nome.But they also have synonyms for any of those elements that you have named. Creare una tupla con nome specificando i nomi per ogni elemento.You create a named tuple by specifying the names for each element. Un modo consiste nello specificare i nomi come parte dell'inizializzazione della tupla:One way is to specify the names as part of the tuple initialization:

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

I sinonimi vengono gestiti dal compilatore e dal linguaggio in modo che sia possibile usare efficacemente le tuple con nome.These synonyms are handled by the compiler and the language so that you can use named tuples effectively. Gli editor e gli IDE possono leggere questi nomi semantici tramite le API di Roslyn.IDEs and editors can read these semantic names using the Roslyn APIs. È possibile fare riferimento agli elementi di una tupla denominata tramite questi nomi semantici in qualsiasi punto dello stesso assembly.You can reference the elements of a named tuple by those semantic names anywhere in the same assembly. Il compilatore sostituisce i nomi definiti con equivalenti Item* durante la generazione dell'output compilato.The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. Il Microsoft Intermediate Language (MSIL) compilato non include i nomi assegnati a questi elementi.The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

A partire da C# 7.1, i nomi dei campi per una tupla possono essere forniti dalle variabili utilizzate per inizializzare la tupla.Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. Si parla di inizializzatori di proiezione tupla .This is referred to as tuple projection initializers. Il codice seguente crea una tupla denominata accumulation con elementi count (integer) e sum (double).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);

Il compilatore deve comunicare tali nomi creati per le tuple, restituiti dalle proprietà o dai metodi pubblici.The compiler must communicate those names you created for tuples that are returned from public methods or properties. In questi casi, il compilatore aggiunge un attributo TupleElementNamesAttribute al metodo.In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. Questo attributo contiene una proprietà elenco TransformNames che presenta i nomi assegnati a ognuno degli elementi nella tupla.This attribute contains a TransformNames list property that contains the names given to each of the elements in the tuple.

Nota

Anche gli strumenti di sviluppo come Visual Studio consentono di leggere i metadati e di fornirli a IntelliSense e ad altre funzionalità tramite i nomi dei campi dei metadati.Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

È importante comprendere questi principi fondamentali delle nuove tuple e del tipo ValueTuple per comprendere le regole per la mutua assegnazione delle tuple con nome.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.

Inizializzatori di proiezione tuplaTuple projection initializers

In generale, gli inizializzatori di proiezione tupla funzionano utilizzando i nomi di variabile o campo dal lato destro di un'istruzione di inizializzazione della 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 viene fornito un nome esplicito, esso ha la precedenza su qualsiasi nome previsto.If an explicit name is given, that takes precedence over any projected name. Nell'inizializzatore seguente, ad esempio, gli elementi sono explicitFieldOne e explicitFieldTwo, non 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);

Per i campi in cui non viene specificato un nome esplicito, viene proiettato un nome implicito applicabile.For any field where an explicit name is not provided, an applicable implicit name is projected. Non è obbligatorio fornire nomi semantici, in modo esplicito o implicito.There is no requirement to provide semantic names, either explicitly or implicitly. L'inizializzatore seguente ha nomi di campo Item1 con valore 42, e stringContent con valore "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);

Esistono due condizioni in cui i nomi dei campi candidati non sono previsti nel campo di tupla:There are two conditions where candidate field names are not projected onto the tuple field:

  1. Quando il nome candidato è un nome di tupla riservato.When the candidate name is a reserved tuple name. Alcuni esempi sono Item3, ToString o Rest.Examples include Item3, ToString, or Rest.
  2. Quando il nome candidato è un duplicato di un altro nome di campo di tupla, implicito o esplicito.When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

Queste condizioni evitano ogni ambiguità.These conditions avoid ambiguity. Questi nomi creerebbero un'ambiguità se utilizzati come nomi di campo per un campo in una tupla.These names would cause an ambiguity if they were used as the field names for a field in a tuple. Nessuna di queste condizioni causa errori in fase di compilazione.Neither of these conditions cause compile-time errors. Al contrario, per gli elementi senza nomi previsti non esistono nomi semantici previsti.Instead, the elements without projected names do not have semantic names projected for them. Gli esempi seguenti illustrano queste condizioni: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);

Queste situazioni non causano errori del compilatore perché sarebbe una modifica di rilievo per il codice scritto con C# 7.0, quando le proiezioni dei nomi di campo di tupla non erano disponibili.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.

Uguaglianza e tupleEquality and tuples

A partire da C# 7.3, i tipi tupla supportano gli operatori == e !=.Beginning with C# 7.3, tuple types support the == and != operators. Questi operatori confrontano ogni membro dell'argomento a sinistra con ogni membro dell'argomento a destra in ordine.These operators work by comparing each member of the left argument to each member of the right argument in order. Questi confronti generano un corto circuito.These comparisons short-circuit. Interromperanno la valutazione dei membri non appena una coppia è diversa.They will stop evaluating members as soon as one pair is not equal. Gli esempi di codice seguenti usano ==, ma tutte le regole di confronto si applicano anche a !=.The following code examples use ==, but the comparison rules all apply to !=. L'esempio di codice seguente illustra un confronto di uguaglianza per due coppie di interi: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'

Esistono diverse regole che rendono più comodi i test di uguaglianza delle tuple.There are several rules that make tuple equality tests more convenient. I test di uguaglianza delle tuple eseguono conversioni con elevazione se una delle tuple è una tupla nullable, come illustrato nel codice seguente: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

I test di uguaglianza delle tuple eseguono anche conversioni implicite per ogni membro di entrambe le tuple,Tuple equality also performs implicit conversions on each member of both tuples. incluse conversioni con elevazione, conversioni verso un tipo di dati più grande o altre conversioni implicite.These include lifted conversions, widening conversions, or other implicit conversions. Gli esempi seguenti mostrano che una tupla a 2 elementi integer può essere confrontata con una tupla a 2 elementi long a causa della conversione implicita da integer a long: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

I nomi dei membri della tupla non partecipano ai test per l'uguaglianza.The names of the tuple members do not participate in tests for equality. Tuttavia, se uno degli operandi è una valore letterale di tupla con nomi espliciti, il compilatore genera l'avviso CS8383 se tali nomi non corrispondono ai nomi dell'altro 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. Nel caso in cui entrambi gli operandi sono valori letterali di tupla, l'avviso viene generato per l'operando di destra, come illustrato nell'esempio seguente: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

Infine, le tuple possono contenere tuple annidate.Finally, tuples may contain nested tuples. L'uguaglianza delle tuple confronta la "forma" di ogni operando tramite tuple annidate, come illustrato nell'esempio seguente: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)) );

È un errore di compilazione confrontare due tuple per verificarne l'uguaglianza (o la disuguaglianza) quando hanno forme diverse.It's a compile time error to compare two tuples for equality (or inequality) when they have different shapes. Il compilatore non prova a decostruire le tuple annidate per confrontarle.The compiler won't attempt any deconstruction of nested tuples in order to compare them.

Assegnazione e tupleAssignment and tuples

Il linguaggio supporta l'assegnazione tra tipi tupla con lo stesso numero di elementi, in cui ogni elemento sul lato destro può essere convertito in modo implicito nell'elemento sul lato sinistro corrispondente.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. Non vengono considerate altre conversioni per le assegnazioni.Other conversions aren't considered for assignments. È un errore in fase di compilazione assegnare una tupla a un'altra quando hanno forme diverse.It's a compile time error to assign one tuple to another when they have different shapes. Il compilatore non tenterà la decostruzione di tuple annidate per assegnarle.The compiler won't attempt any deconstruction of nested tuples in order to assign them. Esaminiamo i tipi di assegnazioni consentiti tra i tipi di tupla.Let's look at the kinds of assignments that are allowed between tuple types.

Considerare le variabili usate negli esempi seguenti: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");

Le prime due variabili, unnamed e anonymous, non hanno nomi semantici forniti per gli elementi.The first two variables, unnamed and anonymous do not have semantic names provided for the elements. I nomi dei campi sono Item1 e Item2.The field names are Item1 and Item2. Le ultime due variabili, named e differentName, hanno nomi semantici attribuiti agli elementi.The last two variables, named and differentName have semantic names given for the elements. Queste due tuple presentano nomi diversi per gli elementi.These two tuples have different names for the elements.

Tutte e quattro le tuple hanno lo stesso numero di elementi (noto come "cardinalità") e i tipi di tali elementi sono identici.All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. Pertanto, tutte queste assegnazioni funzionano: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;

Si noti che non sono assegnati i nomi delle tuple.Notice that the names of the tuples are not assigned. I valori degli elementi vengono assegnati seguendo l'ordine degli elementi nella tupla.The values of the elements are assigned following the order of the elements in the tuple.

Le tuple di tipi diversi o con numeri di elementi diversi non possono essere assegnate: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;

Tuple come valori restituiti dal metodoTuples as method return values

Uno degli usi più comuni per le tuple è come valore restituito da metodo.One of the most common uses for tuples is as a method return value. Di seguito viene illustrato un esempio.Let's walk through one example. Si consideri il metodo che calcola la deviazione standard per una sequenza di numeri: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

Questi esempi calcolano la deviazione standard non corretta di esempio.These examples compute the uncorrected sample standard deviation. La formula di deviazione standard non corretta di esempio consiste nel dividere la somma dei quadrati delle differenze rispetto al valore medio per (N-1) invece di N, come fa il metodo di estensione 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. Per altre informazioni sulle differenze tra queste formule di deviazione standard, consultare un testo di statistica.Consult a statistics text for more details on the differences between these formulas for standard deviation.

Il codice precedente segue la formula canonica per la deviazione standard.The preceding code follows the textbook formula for the standard deviation. Genera la risposta corretta, ma si tratta di un'implementazione inefficiente.It produces the correct answer, but it's an inefficient implementation. Questo metodo enumera la sequenza due volte: una volta per produrre la media e una volta per produrre la media del quadrato della differenza della media.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. Si noti che le query LINQ vengono valutate in modo differito, quindi il calcolo delle differenze rispetto al valore medio e la media tra tali differenze genera un'unica enumerazione.(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.)

Esiste una formula alternativa che calcola la deviazione standard usando solo un'enumerazione della sequenza.There is an alternative formula that computes standard deviation using only one enumeration of the sequence. Questo calcolo produce due valori durante l'enumerazione della sequenza: la somma di tutti gli elementi nella sequenza e la somma di ogni valore quadrato: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);
}

Questa versione enumera la sequenza esattamente una volta.This version enumerates the sequence exactly once. Tuttavia, non si tratta di codice riutilizzabile.But it's not reusable code. Continuando a lavorare, si scoprirà che numerosi calcoli statistici usano il numero di elementi nella sequenza, la somma della sequenza e la somma dei quadrati della sequenza.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. Eseguiamo il refactoring di questo metodo e scriviamo un metodo di utilità che produce tutti e tre questi valori.Let's refactor this method and write a utility method that produces all three of those values. Tutti e tre i valori possono essere restituiti come tupla.All three values can be returned as a tuple.

Aggiorniamo questo metodo in modo tale che i tre valori calcolati durante l'enumerazione vengano archiviati in una tupla.Let's update this method so the three values computed during the enumeration are stored in a tuple. Questa operazione consente di creare questa versione: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);
}

Il supporto del refactoring di Visual Studio semplifica l'estrazione delle funzionalità per le statistiche principali in un metodo privato.Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. Ciò offre un metodo private static che restituisce il tipo di tupla con tre valori di 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;
}

Il linguaggio fornisce due opzioni aggiuntive che è possibile usare se si desidera apportare manualmente rapide modifiche.The language enables a couple more options that you can use, if you want to make a few quick edits by hand. In primo luogo, è possibile usare la dichiarazione var per inizializzare il risultato della tupla dalla chiamata al metodo ComputeSumAndSumOfSquares.First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. È anche possibile creare tre variabili discrete all'interno del metodo ComputeSumAndSumOfSquares.You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. La versione finale è illustrata nel codice seguente: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);
}

Questa versione finale può essere usata per qualsiasi metodo che necessiti di questi tre valori o di sottoinsiemi di essi.This final version can be used for any method that needs those three values, or any subset of them.

Il linguaggio supporta altre opzioni per la gestione dei nomi degli elementi in questi metodi che restituiscono tuple.The language supports other options in managing the names of the elements in these tuple-returning methods.

È possibile rimuovere i nomi dei campi dalla dichiarazione di valore restituito e restituire una tupla senza 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);
}

I campi di questa tupla sono denominati Item1, Item2 e Item3.The fields of this tuple are named Item1, Item2, and Item3. Si consiglia di fornire nomi semantici agli elementi delle tuple restituite dai metodi.It's recommended that you provide semantic names to the elements of tuples returned from methods.

Un altro contesto in cui le tuple possono risultare utili è durante la creazione di query LINQ.Another idiom where tuples can be useful is when you are authoring LINQ queries. Il risultato proiettato finale spesso contiene alcune, ma non tutte le proprietà degli oggetti selezionati.The final projected result often contains some, but not all, of the properties of the objects being selected.

In genere, i risultati della query vengono proiettati in una sequenza di oggetti del tipo anonimo.You would traditionally project the results of the query into a sequence of objects that were an anonymous type. Ciò presenta numerose limitazioni, principalmente perché i tipi anonimi non possono essere facilmente denominati nel tipo restituito per un metodo.That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. In alternativa, usare object o dynamic come tipo del risultato offre costi significativi in termini di prestazioni.Alternatives using object or dynamic as the type of the result came with significant performance costs.

La restituzione di una sequenza di un tipo di tupla è semplice e i nomi e i tipi degli elementi sono disponibili in fase di compilazione e tramite gli strumenti 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. Si consideri, ad esempio, un'applicazione ToDo.For example, consider a ToDo application. È possibile definire una classe simile alla seguente per rappresentare una singola voce nell'elenco ToDo: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; }    
}

Le applicazioni per dispositivi mobili possono supportare un modulo compresso delle voci ToDo correnti che visualizza solo il titolo.Your mobile applications may support a compact form of the current ToDo items that only displays the title. Questa query LINQ genera una proiezione che include solo l'ID e il titolo.That LINQ query would make a projection that includes only the ID and the title. Un metodo che restituisce una sequenza di tuple esprime in maniera accurata questa struttura: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

In C# 7.1, le proiezioni di tupla consentono di creare tuple con nome utilizzando gli elementi, in modo simile alla denominazione di proprietà in tipi anonimi.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. Nel codice precedente, l'istruzione select nella proiezione di query crea una tupla che contiene elementi ID e Title.In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

La tupla con nome può essere parte della firma.The named tuple can be part of the signature. Consente al compilatore e agli strumenti IDE di garantire in modo statico che si sta usando correttamente il risultato.It lets the compiler and IDE tools provide static checking that you are using the result correctly. La tupla con nome contiene anche le informazioni sul tipo statico in modo da eliminare la necessità di usare le costose funzionalità di runtime quali la reflection o l'associazione dinamica al fine di lavorare con i risultati.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.

DecostruzioneDeconstruction

È possibile decomprimere tutte le voci in una tupla decostruendo la tupla restituita da un metodo.You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. Per la decostruzione delle tuple esistono tre diversi approcci.There are three different approaches to deconstructing tuples. È prima possibile dichiarare il tipo di ogni campo all'interno di parentesi per creare variabili discrete per ognuno degli elementi nella 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);
}

È anche possibile dichiarare variabili tipizzate in modo implicito per ogni campo in una tupla usando la parola chiave var all'esterno delle parentesi: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);
}

In aggiunta, è possibile usare la parola chiave var con una o tutte le dichiarazioni di variabili all'interno delle parentesi.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);

Non è possibile usare un tipo specifico all'esterno delle parentesi, anche se ogni campo nella tupla presenta lo stesso tipo.You cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

È possibile decostruire le tuple anche con le dichiarazioni esistenti: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);
}

Avviso

Non è possibile combinare le dichiarazioni esistenti con dichiarazioni all'interno di parentesi.You cannot mix existing declarations with declarations inside the parentheses. Ad esempio, la dichiarazione seguente non è consentita: (var x, y) = MyMethod();.For instance, the following is not allowed: (var x, y) = MyMethod();. Questa operazione genera un errore CS8184 perché x è dichiarato all'interno delle parentesi e y è già stato dichiarato in precedenza in un'altra posizione.This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

Decostruzione dei tipi definiti dall'utenteDeconstructing user-defined types

Qualsiasi tipo di tupla può essere decostruito come illustrato in precedenza.Any tuple type can be deconstructed as shown above. È anche semplice abilitare la decostruzione sui tipi definiti dall'utente (classi, struct o perfino interfacce).It's also easy to enable deconstruction on any user-defined type (classes, structs, or even interfaces).

L'autore del tipo può definire uno o più metodi Deconstruct che assegnano valori a qualsiasi numero di variabili out che rappresentano gli elementi di dati di cui il tipo è composto.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. Ad esempio, il tipo Person seguente definisce un metodo Deconstruct che decostruisce un oggetto persona negli elementi che ne rappresentano nome e cognome: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;
    }
}

Il metodo di decostruzione consente l'assegnazione da un Person a due stringhe, che rappresentano le proprietà 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;

È possibile abilitare la decostruzione anche per i tipi non creati dall'utente.You can enable deconstruction even for types you did not author. Il metodo Deconstruct può essere un metodo di estensione che decomprime i membri di dati accessibili di un oggetto.The Deconstruct method can be an extension method that unpackages the accessible data members of an object. L'esempio seguente mostra un tipo Student, derivato dal tipo Person e un metodo di estensione che decostruisce un Student in tre variabili, che rappresentano FirstName, LastName e 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 oggetto Student dispone ora di due metodi Deconstruct di accesso: il metodo di estensione dichiarato per i tipi Student e i membri 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. Entrambi appartengono all'ambito, consentendo la decostruzione di un Student in due o tre variabili.Both are in scope, and that enables a Student to be deconstructed into either two variables or three. Se si assegna uno studente a tre variabili, vengono restituiti nome, cognome e GPA.If you assign a student to three variables, the first name, last name, and GPA are all returned. Se si assegna uno studente a due variabili, vengono restituiti solo nome e cognome.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;

È necessario prestare attenzione alla definizione di più metodi Deconstruct in una classe o in una gerarchia di classi.You should be careful defining multiple Deconstruct methods in a class or a class hierarchy. Più metodi Deconstruct che presentano lo stesso numero di parametri out possono rapidamente causare ambiguità.Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. I chiamanti potrebbero non essere in grado di eseguire facilmente chiamate al metodo Deconstruct desiderato.Callers may not be able to easily call the desired Deconstruct method.

In questo esempio, vi è una minima possibilità di chiamata ambigua perché il metodo Deconstruct per Person ha due parametri di output e il metodo Deconstruct per Student ne ha tre.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.

Gli operatori di decostruzione non partecipano ai test di uguaglianza.Deconstruction operators do not participate in testing equality. L'esempio seguente genera l'errore di compilazione CS0019:The following example generates compiler error CS0019:

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

Il metodo Deconstruct potrebbe convertire l'oggetto Person p in una tupla che contiene due stringhe, ma ciò non è applicabile nel contesto dei test di uguaglianza.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.

Tuple come parametri outTuples as out parameters

Le tuple possono essere utilizzate come parametri out.Tuples can be used as out parameters themselves. Da non confondere con qualsiasi ambiguità citata in precedenza nella sezione decostruzione .Not to be confused with any ambiguity previously mentioned in the Deconstruction section. In una chiamata al metodo è necessario descrivere solo la forma della tupla:In a method call, you need only describe the tuple's shape:

Dictionary<int, (int, string)> dict = new Dictionary<int, (int, string)>();
dict.Add(1, (234, "First!"));
dict.Add(2, (345, "Second"));
dict.Add(3, (456, "Last"));

// TryGetValue already demonstrates using out parameters
dict.TryGetValue(2, out (int num, string place) pair);

Console.WriteLine($"{pair.num}: {pair.place}");

/*
 * Output:
 * 345: Second
 */

In alternativa, è possibile usare una tupla senza nome e fare riferimento ai relativi campi come Item1 e Item2:Alternatively, you could use an unnamed tuple and refer to its fields as Item1 and Item2:

dict.TryGetValue(2, out (int, string) pair);
// ...
Console.WriteLine($"{pair.Item1}: {pair.Item2}");

ConclusioneConclusion

Il nuovo supporto per linguaggio e libreria per le tuple con nome rende più semplice lavorare con schemi che usano strutture di dati che archiviano più elementi, ma non definiscono il comportamento, come le classi e gli struct.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. Usare le tuple per questi tipi è semplice e veloce.It's easy and concise to use tuples for those types. Si ottengono tutti i vantaggi del controllo del tipo statico, senza la necessità di creare tipi tramite la sintassi più dettagliata 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. Anche in questo caso, risultano particolarmente utili per i metodi di utilità private o internal.Even so, they are most useful for utility methods that are private, or internal. Creare tipi definiti dall'utente, tipi class o struct quando i metodi pubblici restituiscono un valore che contiene più elementi.Create user-defined types, either class or struct types when your public methods return a value that has multiple elements.