Scelta tra tipi anonimi e tupla
La scelta del tipo appropriato richiede di valutarne l'usabilità, le prestazioni e i compromessi rispetto ad altri tipi. I tipi anonimi sono disponibili a partire da C# 3.0, mentre i tipi System.Tuple<T1,T2> generici sono stati introdotti con .NET Framework 4.0. Da allora sono state introdotte nuove opzioni con il supporto a livello di linguaggio, ad esempio System.ValueTuple<T1,T2> che, come suggerisce il nome, fornisce un tipo di valore con la flessibilità dei tipi anonimi. In questo articolo si apprenderà quando è opportuno scegliere un tipo rispetto all'altro.
Usabilità e funzionalità
I tipi anonimi sono stati introdotti in C# 3.0 con espressioni LINQ (Language-Integrated Query). Con LINQ, gli sviluppatori spesso proiettano i risultati delle query in tipi anonimi che contengono alcune proprietà selezionate degli oggetti in uso. Si consideri l'esempio seguente, che crea un'istanza di una matrice di oggetti DateTime e ne esegue l'iterazione proiettandola in un tipo anonimo con due proprietà.
var dates = new[]
{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};
foreach (var anonymous in
dates.Select(
date => new { Formatted = $"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks }))
{
Console.WriteLine($"Ticks: {anonymous.Ticks}, formatted: {anonymous.Formatted}");
}
Per i tipi anonimi vengono create istanze tramite l'operatore new
e i nomi e i tipi di proprietà vengono dedotti dalla dichiarazione. Se due o più inizializzatori di oggetti anonimi nello stesso 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.
Il frammento di codice C# precedente proietta un tipo anonimo con due proprietà, in modo analogo alla classe C# generata dal compilatore seguente:
internal sealed class f__AnonymousType0
{
public string Formatted { get; }
public long Ticks { get; }
public f__AnonymousType0(string formatted, long ticks)
{
Formatted = formatted;
Ticks = ticks;
}
}
Per altre informazioni, vedere Tipi anonimi. La stessa funzionalità esiste con le tuple; quando si proiettano in query LINQ, è possibile selezionare le proprietà in tuple. Queste tuple passano attraverso la query, esattamente come i tipi anonimi. Si consideri l'esempio seguente con System.Tuple<string, long>
.
var dates = new[]
{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};
foreach (var tuple in
dates.Select(
date => new Tuple<string, long>($"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {tuple.Item2}, formatted: {tuple.Item1}");
}
Con System.Tuple<T1,T2>, l'istanza espone le proprietà degli elementi numerati, ad esempio Item1
e Item2
. Questi nomi di proprietà possono rendere più difficile comprendere la finalità dei valori delle proprietà, perché il nome della proprietà fornisce solo l'ordinale. Inoltre, i tipi System.Tuple
fanno riferimento ai tipi class
. Tuttavia System.ValueTuple<T1,T2> è un tipo valore struct
. Il frammento di codice C# seguente usa ValueTuple<string, long>
per la proiezione. In questo modo, assegna l'uso di una sintassi di valori letterali.
var dates = new[]
{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};
foreach (var (formatted, ticks) in
dates.Select(
date => (Formatted: $"{date:MMM dd, yyyy at hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {ticks}, formatted: {formatted}");
}
Per altre informazioni sulle tuple, vedere Tipi di tupla (riferimenti per C#) o Tuple (Visual Basic).
Gli esempi precedenti sono tutti equivalenti a livello funzionale, ma esistono lievi differenze nell'usabilità e nelle implementazioni sottostanti.
Svantaggi
È consigliabile usare sempre ValueTuple rispetto a Tuple e ai tipi anonimi, ma è necessario considerare alcuni compromessi. I tipi ValueTuple sono modificabili, mentre Tuple sono di sola lettura. I tipi anonimi possono essere usati negli alberi delle espressioni, mentre le tuple no. La tabella seguente offre una panoramica di alcune delle principali differenze.
Differenze principali
Nome | Modificatore di accesso | Type | Nome membro personalizzato | Supporto della decostruzione | Supporto dell'albero delle espressioni |
---|---|---|---|---|---|
Tipi anonimi | internal |
class |
✔️ | ❌ | ✔️ |
Tuple | public |
class |
❌ | ❌ | ✔️ |
ValueTuple | public |
struct |
✔️ | ✔️ | ❌ |
Serializzazione
Una considerazione importante quando si sceglie un tipo è se verrà o meno serializzato. La serializzazione è il processo di conversione dello stato di un oggetto in un form che può essere mantenuto o trasportato. Per altre informazioni, vedere Serializzazione. Se la serializzazione è importante, è preferibile creare class
o struct
rispetto ai tipi anonimi o ai tipi di tupla.
Prestazioni
Le prestazioni tra questi tipi dipendono dallo scenario. L'impatto principale comporta il compromesso tra allocazioni e copia. Nella maggior parte degli scenari l'impatto è ridotto. Quando possono verificarsi gravi impatti, è necessario adottare misure per informare la decisione.
Conclusione
Per scegliere tra tuple e tipi anonimi, gli sviluppatori devono considerare diversi fattori. In generale, se non si lavora con alberi delle espressioni e si ha familiarità con la sintassi delle tuple, scegliere ValueTuple perché fornisce un tipo valore con la flessibilità per denominare le proprietà. Se si lavora con alberi delle espressioni e si preferisce denominare le proprietà, scegliere i tipi anonimi. In caso contrario, usare Tuple.