Condividi tramite


Tuple e decostruzione

Suggerimento

Novità dello sviluppo di software? Iniziare con le esercitazioni introduttive . Si incontreranno tuple quando è necessario restituire più valori da un metodo o raggruppare valori senza definire un tipo nominato.

Esperienza in un'altra lingua? Le tuple C# sono tipi di valore simili alle tuple in Python o Swift, ma con elementi denominati facoltativi e supporto completo per la decostruzione. Ignorare le sezioni di decostruzione e uguaglianza per i modelli specifici di C#.

Una tupla raggruppa più valori in una singola struttura leggera senza che sia necessario definire un tipo denominato. I tuple sono un tipo di valore che è possibile dichiarare in linea, restituire dai metodi e scomporre in singole variabili. Usare le tuple quando è necessario un raggruppamento rapido e temporaneo di valori correlati. Ad esempio, quando si restituiscono più risultati da un metodo o si archivia una coppia di coordinate.

L'esempio seguente crea una tupla con elementi denominati e accede a ogni elemento in base al nome:

var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321

Le tuple funzionano bene per i raggruppamenti di breve durata in cui la definizione di una classe, uno struct o un record aggiungerebbe una cerimonia non necessaria. Per i concetti o i tipi di dominio di lunga durata con comportamento, preferire record, classi o struct. Per un confronto tra quando usare ognuna di esse, vedere Scegliere il tipo di tipo.

Dichiarare e inizializzare le tuple

Dichiarare una tupla elencando i tipi di elemento tra parentesi. Facoltativamente, è possibile assegnare un nome a ogni elemento per rendere il codice più leggibile:

// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");

// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");

// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");

Quando non si specificano nomi, gli elementi usano nomi Item1predefiniti , Item2e così via. Gli elementi denominati rendono il codice autodocumentato senza richiedere una definizione di tipo separata.

Nomi di elementi dedotti

Il compilatore deduce i nomi degli elementi dai nomi delle variabili o dalle proprietà usate per inizializzare la tupla. Questa funzionalità evita la ridondanza quando i nomi corrispondono:

var name = "Carol";
var age = 28;

// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28

I nomi dedotti rendono il codice più conciso. Se è necessario un nome di elemento diverso, specificarlo in modo esplicito.

Restituire più valori da un metodo

Uno degli usi più comuni per le tuple è la restituzione di più valori da un metodo. Anziché definire una classe o usare out parametri, restituire una tupla con elementi denominati:

static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
    var min = values.Min();
    var max = values.Max();
    var avg = values.Average();
    return (min, max, avg);
}

Gli elementi di tupla denominati rendono i valori restituiti leggibili sia nel sito di chiamata che nella firma del metodo. Il chiamante può accedere a ogni valore in base al nome senza dover ricordare l'ordine posizionale.

Decostruire le tuple

La decostruzione decomprime gli elementi di una tupla in variabili separate in una singola istruzione. È possibile decostruire i tuple in diversi modi:

var point = (X: 3, Y: 7);

// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");

// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");

// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");

// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");

La decostruzione è particolarmente utile quando si riceve una tupla da una chiamata al metodo ed è necessario lavorare immediatamente con i singoli valori.

È possibile decostruire le tuple direttamente nei foreach cicli, che rende concisa l'iterazione sulle raccolte di valori raggruppati:

List<(string Name, int Score)> results =
[
    ("Alice", 92),
    ("Bob", 87),
    ("Carol", 95)
];

foreach (var (name, score) in results)
{
    Console.WriteLine($"{name}: {score}");
}

Quando non è necessario ogni elemento, usare un valore discard (_) al posto di ogni valore che si vuole ignorare. Usare un oggetto separato _ per ogni posizione eliminata:

List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15

Per altre informazioni sull'uso delle eliminazioni in contesti diversi, vedere Discards.

Uguaglianza delle tuple

È possibile confrontare le tuple usando == e !=. Questi operatori confrontano ogni elemento in ordine, quindi due tuple sono uguali quando tutti gli elementi corrispondenti sono uguali:

var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);

Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False

// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True

L'uguaglianza delle tuple usa l'operatore == definito in ogni tipo di elemento, il che significa che il confronto funziona correttamente per stringhe, numeri e altri tipi che definiscono ==. I nomi degli elementi non influiscono sull'uguaglianza, ma solo sui valori e sulle posizioni.

Mutazione non distruttiva con with

L'espressione with crea una copia di una tupla con uno o più elementi modificati, mantenendo l'originale invariato.

var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };

Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99

Questo modello è utile quando si desidera una variante di una tupla esistente senza modificare l'originale. L'espressione with funziona con le tuple allo stesso modo dei record.

Tuple nei dizionari e operazioni di ricerca

Le tuple rendono utili i valori del dizionario quando è necessario associare una chiave a più parti di dati:

var sizeChart = new Dictionary<string, (int Min, int Max)>
{
    ["Small"] = (0, 50),
    ["Medium"] = (51, 100),
    ["Large"] = (101, 200)
};

if (sizeChart.TryGetValue("Medium", out var range))
{
    Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100

Le tuple funzionano anche come chiavi del dizionario, offrendo una chiave composita senza definire un tipo personalizzato. Poiché le tuple implementano l'uguaglianza strutturale, le ricerche corrispondono ai valori combinati di tutti gli elementi:

var grid = new Dictionary<(int Row, int Column), string>
{
    [(0, 0)] = "Origin",
    [(1, 3)] = "Sensor A",
    [(2, 5)] = "Sensor B"
};

var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
    Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A

Questo modello evita la necessità di una classe separata per semplici ricerche a più chiavi o associare chiavi a più valori.

Tuple e tipi anonimi

Le tuple sono la scelta preferita quando è necessaria una struttura di dati leggera senza nome. I tipi anonimi rimangono disponibili per gli scenari ad albero delle espressioni e per il codice che richiede tipi di riferimento, ma le tuple offrono prestazioni migliori, supporto della decostruzione e sintassi più flessibile. Per altre informazioni sui tipi anonimi, vedere Scelta tra tipi anonimi e tuple.

Vedere anche