Menu

Tuple in C#: tuple con nome, restituire più valori, decostruzione

Una tupla di C# raggruppa pochi valori in uno solo senza dichiarare un tipo. Impara la sintassi delle value tuple, gli elementi con nome, come restituire più valori da un metodo, la decostruzione con var (a, b), le tuple come chiavi di un dizionario, e in cosa ValueTuple differisce dal vecchio System.Tuple.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Una tupla raggruppa un numero piccolo e fisso di valori in un solo valore, senza dichiarare una classe apposta. ("Ana", 31) è una tupla formata da una string e un int. Le tuple sono il modo idiomatico per restituire due o tre cose da un metodo e per tenere coppie di breve durata dentro un metodo.

Creare una tupla

Anche il tipo si scrive con le parentesi: (string, int) è un tipo tupla il cui primo elemento è una string e il secondo un int.

Output:

Ana is 31
(3, 4)
179.70
(Ana, 32)

Senza nomi, gli elementi sono Item1, Item2, Item3 e così via. Queste tuple sono struct System.ValueTuple, quindi assegnarne una a un'altra variabile copia i valori, e modificare la copia lascia intatto l'originale.

Elementi con nome

Item1 non dice nulla su cosa sia il valore. Dai un nome agli elementi, nel tipo oppure nel letterale:

Output:

Ana, 31
Desk lamp costs 24.99
Ana
Ana

I nomi sono una comodità di compilazione. A runtime entrambe le tuple qui sopra sono semplici valori ValueTuple<string, int>, ed è per questo che assegnare person a una tupla con nomi diversi compila: devono corrispondere solo i tipi degli elementi e il loro ordine.

C# 7.1 e successivi deducono anche i nomi dalle variabili del letterale, quindi var t = (name, age); ti dà t.name e t.age senza doverli scrivere due volte. In C# 7.0 quegli elementi sono solo Item1 e Item2.

Restituire più valori da un metodo

È il motivo principale per cui le tuple sono state aggiunte al linguaggio. Prima le opzioni erano i parametri out, una classe usa e getta, o System.Tuple con i suoi anonimi Item1.

Output:

Lowest 60, highest 95
Average 78.75

I nomi degli elementi nel tipo di ritorno diventano i nomi che vede chi chiama. Confronta lo stesso metodo con i parametri out:

static void Stats(int[] scores, out int min, out int max, out double average) { ... }

Stats(scores, out int min, out int max, out double avg);

I parametri out restano la convenzione per la forma TryParse, dove un bool dice se l'operazione è riuscita e il valore torna indietro accanto a esso. Per "calcola diverse cose e restituiscile", una tupla si legge meglio. Vedi ref e out.

Decostruzione

La decostruzione spacchetta una tupla in variabili separate con una sola istruzione:

Output:

Lima 24.5
Lima
24.5
2 1

I nomi delle variabili in var (city, temp) li scegli tu; non devono coincidere con i nomi degli elementi della tupla, perché la decostruzione procede per posizione. Lo scambio (a, b) = (b, a) costruisce prima una tupla con i vecchi valori e poi assegna, quindi nessuno dei due valori va perso.

Anche le tue classi possono supportare la decostruzione dichiarando un metodo Deconstruct con parametri out. Da C# 7 con .NET Core 2.0 o successivi ne ha uno anche KeyValuePair, quindi un ciclo su un dizionario può spacchettare ogni voce direttamente nella sua intestazione:

class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

var (x, y) = new Point(3, 4);

foreach (var (name, score) in scoresByName)   // Dictionary<string, int>
{
    Console.WriteLine($"{name}: {score}");
}

La stessa forma foreach (var (a, b) in listOfTuples) funziona per una lista di tuple. Prendere l'intera tupla come unica variabile del ciclo e leggerne gli elementi con nome, come fa la sezione successiva, funziona altrettanto bene.

Tuple in liste, LINQ e dizionari

Una lista di tuple con nome è un modo leggero per tenere righe di dati dentro un metodo:

Output:

Chloe 95
Ana 88
Ben 72
(Ana, B), (Chloe, A)
Ben
False

ValueTuple implementa Equals e GetHashCode elemento per elemento, quindi (2, 7) costruita in due punti diversi trova la stessa voce del dizionario. Questo rende la tupla la chiave composta più semplice: nessuna classe, nessuna funzione di hash scritta a mano, nessuna concatenazione di stringhe come row + ":" + col.

Uguaglianza delle tuple

Equals confronta elemento per elemento e funziona da quando esistono le tuple:

Output:

True
False

C# 7.3 ha aggiunto == e != per le tuple, che vengono compilati nello stesso confronto elemento per elemento e ignorano anch'essi i nomi:

if ((order.Status, order.Paid) == ("shipped", true)) { ... }
Console.WriteLine((1, "x") == (1, "x"));   // True

System.Tuple vs ValueTuple

.NET 4 ha introdotto System.Tuple, una classe creata con Tuple.Create. È ancora nel framework e compare ancora in codice e API più vecchi, ed è per questo che le ricerche su "C# tuple" finiscono spesso lì.

ValueTuple (C# 7)System.Tuple (.NET 4)
Sintassi(1, "a"), (int, string)Tuple.Create(1, "a"), Tuple<int, string>
Tipostruct (tipo valore)classe (tipo riferimento)
Nomi degli elementiSìNo, solo Item1, Item2
ModificabileSì, gli elementi sono campiNo, proprietà di sola lettura
DecostruzioneSìSì, tramite metodi di estensione

Output:

Ben 25
(Ben, 25)

ToValueTuple() e ToTuple() convertono dall'una all'altra. Nel codice nuovo preferisci le value tuple: non allocano nulla sull'heap e possono avere nomi.

Quando usare invece una classe o un record

Le tuple rendono al meglio quando il raggruppamento è locale e ovvio: un metodo restituisce due numeri, una query LINQ passa una coppia al passo successivo, un dizionario ha bisogno di una chiave in due parti. Passa a un tipo con nome quando:

  • La stessa forma compare in diverse firme di metodi pubblici. (string, string, int) in cinque posti è una classe che nessuno ha ancora scritto.
  • I valori hanno bisogno di comportamento, di validazione, o di più di tre o quattro elementi.
  • I dati attraversano un confine come la serializzazione. I nomi non esistono a runtime, quindi Newtonsoft.Json scrive Item1 e Item2, e System.Text.Json scrive {} perché gli elementi delle tuple sono campi, che per impostazione predefinita salta.

I record di C# 9 ti danno gran parte della comodità di una tupla con un nome vero. record Score(string Name, int Points); ha già uguaglianza di valore, un ToString leggibile e la decostruzione. Vedi record.

Errori comuni

  • Aspettarsi che i nomi sopravvivano a runtime. La reflection e i serializzatori vedono Item1, Item2 (o niente del tutto).
  • Modificare una copia. Le tuple sono struct: var t2 = t1; t2.Item1 = 5; lascia t1 invariata.
  • Tuple con molti elementi. Oltre tre o quattro, un tipo con nome si legge meglio.
  • Confondere l'ordine della decostruzione. È posizionale: con una tupla (Name, Age), var (age, name) = person; mette il nome in age, e siccome var deduce i tipi, niente ti avvisa.

Domande frequenti

Come restituisco più valori da un metodo in C#?

Restituisci una tupla: dichiara il tipo di ritorno come (int Min, int Max) e scrivi return (lowest, highest);. Chi chiama legge result.Min e result.Max, oppure decostruisce con var (min, max) = MinMax(data);. Le alternative sono i parametri out, adatti al pattern TryParse, oppure una piccola classe o un record quando il risultato ha un significato proprio.

Cos'è una tupla con nome in C#?

Una tupla i cui elementi hanno dei nomi invece di Item1, Item2: (string Name, int Age) person = ("Ana", 31); ti permette di scrivere person.Name. I nomi esistono solo in fase di compilazione; sotto resta una ValueTuple<string, int>, e Item1 continua a funzionare.

Qual è la differenza tra Tuple e ValueTuple in C#?

System.Tuple (da .NET 4) è una classe: allocata sull'heap, immutabile, e i suoi elementi sono sempre e solo Item1, Item2. System.ValueTuple (C# 7) è una struct con campi modificabili, supporta i nomi degli elementi e la sintassi (a, b), ed è quello che crea ogni letterale di tupla. Nel codice nuovo usa le value tuple.

Come funziona la decostruzione delle tuple in C#?

var (name, age) = person; dichiara due variabili e assegna loro in ordine gli elementi della tupla. Puoi anche assegnare a variabili esistenti con (a, b) = (b, a), che le scambia, e saltare elementi con il discard _: var (_, age) = person;.

Posso confrontare le tuple con == in C#?

Da C# 7.3, == e != confrontano le tuple elemento per elemento: (1, "a") == (1, "a") è true. I nomi vengono ignorati; contano solo posizioni e valori. Prima di C# 7.3, usa Equals, che le value tuple implementano nello stesso modo.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA