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.

Vedi anche