Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Gli sviluppatori usano generics sempre in .NET, sia in modo implicito che in modo esplicito. Ti sei mai accorto che stai lavorando con IEnumerable<T> quando usi LINQ in .NET? In alternativa, se è stato visualizzato un esempio online di un "repository generico" per comunicare con i database usando Entity Framework, si è visto che la maggior parte dei metodi restituisce IQueryable<T>
? Ci si potrebbe chiedere che cos'è il T in questi esempi e perché è lì dentro.
Introdotti nel .NET Framework 2.0, i generics sono essenzialmente un "modello di codice" che consente agli sviluppatori di definire strutture di dati sicure per i tipi senza impegnarsi con un tipo di dato specifico. Ad esempio, List<T> è una raccolta generica che può essere dichiarata e usata con qualsiasi tipo, ad esempio List<int>
, List<string>
o List<Person>
.
Per comprendere perché i generics sono utili, si esaminerà una classe specifica prima e dopo l'aggiunta di generics: ArrayList. In .NET Framework 1.0 gli ArrayList
elementi erano di tipo Object. Qualsiasi elemento aggiunto alla raccolta è stato convertito automaticamente in un oggetto Object
. Lo stesso avviene durante la lettura degli elementi dall'elenco. Questo processo, noto come boxing e unboxing, influisce sulle prestazioni. A parte le prestazioni, tuttavia, non c'è modo di determinare il tipo di dati nell'elenco in fase di compilazione, che rende un codice fragile. I generics risolvono questo problema definendo il tipo di dati che ogni istanza dell'elenco conterrà. Ad esempio, è possibile aggiungere solo numeri interi a List<int>
e aggiungere solo persone a List<Person>
.
I generics sono disponibili anche in fase di esecuzione. Il runtime conosce il tipo di struttura dei dati in uso e può archiviarlo in memoria in modo più efficiente.
L'esempio seguente è un piccolo programma che illustra l'efficienza di conoscere il tipo di struttura dei dati in fase di esecuzione:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace GenericsExample {
class Program {
static void Main(string[] args) {
//generic list
List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
//non-generic list
ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
// timer for generic list sort
Stopwatch s = Stopwatch.StartNew();
ListGeneric.Sort();
s.Stop();
Console.WriteLine($"Generic Sort: {ListGeneric} \n Time taken: {s.Elapsed.TotalMilliseconds}ms");
//timer for non-generic list sort
Stopwatch s2 = Stopwatch.StartNew();
ListNonGeneric.Sort();
s2.Stop();
Console.WriteLine($"Non-Generic Sort: {ListNonGeneric} \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();
}
}
}
Questo programma produce output simile al seguente:
Generic Sort: System.Collections.Generic.List`1[System.Int32]
Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
Time taken: 0.2592ms
La prima cosa che si può notare qui è che l'ordinamento dell'elenco generico è notevolmente più veloce rispetto all'ordinamento dell'elenco non generico. È anche possibile notare che il tipo per l'elenco generico è distinto ([System.Int32]), mentre il tipo per l'elenco non generico è generalizzato. Poiché il runtime sa che il generico List<int>
è di tipo Int32, può archiviare gli elementi dell'elenco in una matrice integer sottostante in memoria, mentre il non generico ArrayList
deve eseguire il cast di ogni elemento dell'elenco a un oggetto . Come illustrato in questo esempio, i cast aggiuntivi richiedono tempo e rallentano l'ordinamento dell'elenco.
Un ulteriore vantaggio del runtime che conosce il tipo del tuo generico è una migliore esperienza di debug. Quando si esegue il debug di un generico in C#, si conosce il tipo di ogni elemento nella struttura dei dati. Senza generics, non avresti idea del tipo che ogni elemento era.