Condividi tramite


Tuple e tipi anonimi

Le tuple forniscono una struttura di dati leggera per più membri in una singola struttura. Sono la scelta preferita rispetto ai tipi anonimi. Le tuple forniscono prestazioni migliori, supportano la decostruzione e offrono una sintassi più flessibile.

I tipi anonimi offrono un modo pratico per incapsulare un set di proprietà di sola lettura in un singolo oggetto, senza dover definire prima un tipo in modo esplicito. Il compilatore genera il nome del tipo e non è disponibile a livello di codice sorgente. Il compilatore deduce il tipo di ogni proprietà. Usare i tipi anonimi principalmente quando è necessario il supporto dell'albero delle espressioni o quando si usa il codice che richiede tipi di riferimento.

Tuple e tipi anonimi

Sia le tuple che i tipi anonimi consentono di raggruppare più valori senza definire un tipo denominato. Tuttavia, le tuple godono di un supporto linguistico migliore e compilano in una struttura dati più efficiente. Nella tabella seguente sono riepilogate le differenze principali:

Caratteristica / Funzionalità Tipi anonimi Tuple
TIPO Tipo di riferimento (class) Tipo valore (struct)
Performance Allocazione dell'heap Allocazione dello stack (migliori prestazioni)
Mutability Proprietà di sola lettura Campi modificabili
Decostruzione Non supportato Sostenuto
Alberi delle espressioni Sostenuto Non supportato
Modificatore di accesso internal public
Nomi dei membri Obbligatorio o dedotto Facoltativo (con nomi predefiniti come Item1, Item2)

Quando usare le tuple

Usare le tuple quando:

  • Sono necessarie prestazioni migliori tramite l'allocazione dello stack.
  • Si desidera decostruire i valori in variabili separate.
  • Si restituiscono più valori da un metodo.
  • Non è necessario il supporto dell'albero delle espressioni.

L'esempio seguente illustra come le tuple forniscono funzionalità simili ai tipi anonimi con sintassi più pulita:

// Tuple with named elements.
var tupleProduct = (Name: "Widget", Price: 19.99M);
Console.WriteLine($"Tuple: {tupleProduct.Name} costs ${tupleProduct.Price}");

// Equivalent example using anonymous types.
var anonymousProduct = new { Name = "Widget", Price = 19.99M };
Console.WriteLine($"Anonymous: {anonymousProduct.Name} costs ${anonymousProduct.Price}");

Decostruzione della tupla

È possibile decostruire una tupla in variabili separate, che offre un modo pratico per lavorare con singoli elementi di tupla. C# supporta diversi modi per decostruire le tuple:

static (string Name, int Age, string City) GetPersonInfo()
{
    return ("Alice", 30, "Seattle");
}
// Deconstruct using var for all variables
var (name, age, city) = GetPersonInfo();
Console.WriteLine($"{name} is {age} years old and lives in {city}");
// Output: Alice is 30 years old and lives in Seattle

// Deconstruct with explicit types
(string personName, int personAge, string personCity) = GetPersonInfo();
Console.WriteLine($"{personName}, {personAge}, {personCity}");

// Deconstruct into existing variables
string existingName;
int existingAge;
string existingCity;
(existingName, existingAge, existingCity) = GetPersonInfo();

// Deconstruct and discard unwanted values using the discard pattern (_)
var (name2, _, city2) = GetPersonInfo();
Console.WriteLine($"{name2} lives in {city2}");
// Output: Alice lives in Seattle

La decostruzione è utile nei cicli e negli scenari di pattern matching.

var people = new List<(string Name, int Age)>
{
    ("Bob", 25),
    ("Carol", 35),
    ("Dave", 40)
};

foreach (var (personName2, personAge2) in people)
{
    Console.WriteLine($"{personName2} is {personAge2} years old");
}

Tuple come tipo restituito di metodo

Un caso d'uso comune per le tuple è come tipo di ritorno del metodo. Anziché definire i parametri out, è possibile raggruppare i risultati di un metodo in una tupla. Non è possibile restituire un tipo anonimo da un metodo, perché non ha un nome e il tipo restituito non può essere dichiarato.

L'esempio seguente illustra l'uso di tuple con ricerche di dizionario per restituire intervalli di configurazione:

var configLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (configLookup.TryGetValue(4, out (int Min, int Max) range))
{
    Console.WriteLine($"Found range: min is {range.Min}, max is {range.Max}");
}
// Output: Found range: min is 10, max is 20

Questo modello è utile quando si usano metodi che devono restituire sia un indicatore di esito positivo che più valori di risultato. La tupla consente di usare campi denominati (Min e Max) anziché nomi generici come Item1 e Item2, rendendo il codice più leggibile e autodocumentato.

Quando usare tipi anonimi

Usare tipi anonimi quando:

  • Si lavora con alberi delle espressioni (ad esempio, in alcuni provider di Microsoft Language-Integrated Query (LINQ)).
  • È necessario che l'oggetto sia un tipo di riferimento.

Lo scenario più comune consiste nell'inizializzare un tipo anonimo con proprietà di un altro tipo. Nell'esempio seguente si presuppone l'esistenza di una classe denominata Product. La classe Product include Color e Price proprietà, insieme ad altre proprietà a cui non sei interessato.

class Product
{
    public string? Color { get; init; }
    public decimal Price { get; init; }
    public string? Name { get; init; }
    public string? Category { get; init; }
    public string? Size { get; init; }
}

La dichiarazione di tipo anonimo inizia con l'operatore new insieme a un inizializzatore di oggetto. La dichiarazione inizializza un nuovo tipo che usa solo due proprietà della classe Product. I tipi anonimi vengono in genere usati nella select clausola di un'espressione di query per restituire una quantità minore di dati. Per altre informazioni sulle query, vedere LINQ in C#.

Se non si specificano i nomi dei membri nel tipo anonimo, il compilatore assegna ai membri di tipo anonimo lo stesso nome della proprietà usata per inizializzarli. Specificare un nome per una proprietà inizializzata con un'espressione, come illustrato nell'esempio precedente.

Nell'esempio seguente i nomi delle proprietà del tipo anonimo sono Color e Price. Le istanze sono elementi della products raccolta di Product tipi:

var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

Inizializzatori di proiezione con tipi anonimi

I tipi anonimi supportano gli inizializzatori di proiezione, che consentono di usare direttamente variabili o parametri locali senza specificare esplicitamente il nome del membro. Il compilatore deduce i nomi dei membri dai nomi delle variabili. L'esempio seguente illustra questa sintassi semplificata:

// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };

// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };

// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");

Questa sintassi semplificata è utile quando si creano tipi anonimi con molte proprietà:

var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;

// Using projection initializers.
var employee = new { title, department, salary };

// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };

Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");

Il nome del membro non viene dedotto nei casi seguenti:

  • Il nome candidato duplica un altro membro della stessa struttura anonima, esplicito o implicito.
  • Il nome del candidato non è un identificatore valido, ad esempio contiene spazi o caratteri speciali.

In questi casi, è necessario specificare in modo esplicito il nome del membro.

Suggerimento

È possibile usare la regola di stile IDE0037 di .NET per stabilire se sono da preferire i nomi dei membri dedotti o espliciti.

È anche possibile definire un campo usando un oggetto di un altro tipo: classe, struct o anche un altro tipo anonimo. A tale scopo, usare la variabile che contiene questo oggetto. Nell'esempio seguente vengono illustrati due tipi anonimi che usano tipi già definiti dall'utente. In entrambi i casi, il product campo nei tipi shipment anonimi e shipmentWithBonus è di tipo Product e contiene i valori predefiniti di ogni campo. Il bonus campo è di un tipo anonimo creato dal compilatore.

var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };

In genere, quando si usa un tipo anonimo per inizializzare una variabile, è necessario dichiarare la variabile come locale tipizzata in modo implicito tramite var. Non è possibile specificare il nome del tipo nella dichiarazione della variabile perché solo il compilatore ha accesso al nome sottostante del tipo anonimo. Per altre informazioni su var, vedere Variabili locali tipizzate in modo implicito.

È possibile creare una matrice di elementi tipizzati in modo anonimo combinando una variabile locale tipizzata in modo implicito e una matrice tipizzata in modo implicito, come illustrato nell'esempio seguente.

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

I tipi anonimi sono class tipi che derivano direttamente da object, e non è possibile eseguirne il cast in alcun tipo ad eccezione di object. Il compilatore fornisce un nome per ogni tipo anonimo, anche se l'applicazione non può accedervi. Dal punto di vista di Common Language Runtime, un tipo anonimo non è diverso da qualsiasi altro tipo di riferimento.

Se due o più inizializzatori di oggetti anonimi in un assembly specificano una sequenza di proprietà nello stesso ordine e con gli stessi nomi e tipi, il compilatore considera gli oggetti come istanze dello stesso tipo. Condividono le stesse informazioni sul tipo generate dal compilatore.

I tipi anonimi supportano la mutazione non distruttiva sotto forma di con espressioni. Questa funzionalità consente di creare una nuova istanza di un tipo anonimo in cui una o più proprietà hanno nuovi valori:

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

Non è possibile dichiarare un campo, una proprietà, un evento o il tipo restituito di un metodo come tipo anonimo. Analogamente, non è possibile dichiarare un parametro formale di un metodo, una proprietà, un costruttore o un indicizzatore come tipo anonimo. Per passare un tipo anonimo o una raccolta che contiene tipi anonimi, come argomento di un metodo, è possibile dichiarare il parametro come tipo object. Tuttavia, l'uso di object per i tipi anonimi vanifica lo scopo della tipizzazione forte. Se è necessario archiviare i risultati delle query o passarli oltre i limiti del metodo, si consideri l'uso di uno struct o una classe con nome normale invece di un tipo anonimo.

I metodi Equals e GetHashCode nei tipi anonimi sono definiti in termini di metodi delle proprietà Equals e GetHashCode, di conseguenza due istanze dello stesso tipo anonimo sono uguali solo se tutte le relative proprietà sono uguali.

Nota

Il livello di accessibilità di un tipo anonimo è internal. Di conseguenza, due tipi anonimi definiti in assembly diversi non sono dello stesso tipo. Pertanto, le istanze di tipi anonimi non possono essere uguali tra loro se definite in assembly diversi, anche quando hanno tutte le relative proprietà uguali.

I tipi anonimi eseguono l'override del metodo ToString, concatenando il nome e ToString output di ogni proprietà racchiusa tra parentesi graffe.

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

Vedere anche