Generics nel runtime (Guida per programmatori C#)

Quando un tipo o un metodo generico viene compilato in Microsoft Intermediate Language (MSIL), contiene metadati che lo identificano come contenente parametri di tipo. L'uso di MSIL per un tipo generico differisce a seconda che il parametro di tipo sia un tipo valore o un tipo riferimento.

La prima volta che viene costruito un tipo generico con un tipo valore come parametro, il runtime crea un tipo generico specializzato con il parametro o i parametri specificati sostituiti nelle posizioni appropriate in MSIL. I tipi generici specializzati vengono creati una sola volta per ogni tipo valore univoco usato come parametro.

Si supponga, ad esempio, che il codice del programma abbia dichiarato uno stack costruito di numeri interi:

Stack<int>? stack;

A questo punto, il runtime genera una versione specializzata della classe Stack<T> sostituendo l'intero nel modo appropriato per il parametro. D'ora in poi, ogni volta che il codice programma usa uno stack di interi, il runtime riutilizzerà la classe Stack<T> specializzata generata. Nell'esempio seguente vengono create due istanze di uno stack di interi che condividono un'istanza singola nel codice Stack<int>:

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

Tuttavia, si supponga che venga creata un'altra classe Stack<T> con un tipo valore diverso, ad esempio long, o una struttura definita dall'utente come parametro in un altro punto del codice. Di conseguenza, il runtime genererà un'altra versione del tipo generico e sostituirà un valore long nelle posizioni appropriate in MSIL. Le conversioni non sono più necessarie, perché ogni classe generica specializzata contiene il tipo valore in modo nativo.

I generics hanno un funzionamento leggermente diverso con i tipi riferimento. La prima volta che viene costruito un tipo generico con qualsiasi tipo riferimento, il runtime crea un tipo generico specializzato con riferimenti a oggetti sostituiti per i parametri in MSIL. Quindi, ogni volta che viene creata un'istanza di un tipo costruito con un tipo riferimento come parametro, indipendentemente dal tipo, il runtime riutilizza la versione specializzata precedentemente creata del tipo generico. Questo è possibile perché tutti i riferimenti hanno le stesse dimensioni.

Ad esempio, si supponga di avere due tipi riferimento, una classe Customer e una classe Order, e di aver creato uno stack di tipi Customer:

class Customer { }
class Order { }
Stack<Customer> customers;

A questo punto, il runtime genera una versione specializzata della classe Stack<T> in cui sono archiviati riferimenti a oggetti che verranno compilati successivamente invece di archiviare i dati. Si supponga che la riga di codice successiva crei uno stack di un altro tipo riferimento, chiamato Order:

Stack<Order> orders = new Stack<Order>();

Diversamente dai tipi valore, non viene creata un'altra versione specializzata della classe Stack<T> per il tipo Order. Viene invece creata un'istanza della versione specializzata della classe Stack<T> e viene impostata la variabile orders per referenziarla. Si supponga di individuare una riga di codice per la creazione di uno stack di un tipo Customer:

customers = new Stack<Customer>();

Come per l'uso precedente della classe Stack<T> creata con il tipo Order, verrà creata un'altra istanza della classe Stack<T> specializzata. I puntatori contenuti vengono impostati in modo da fare riferimento a un'area di memoria delle dimensioni di un tipo Customer. Poiché il numero di tipi riferimento può variare ampiamente a seconda del programma, l'implementazione C# di generics riduce notevolmente la quantità di codice limitando a uno il numero di classi specializzate create dal compilatore per classi generiche di tipi riferimento.

Inoltre, quando viene creata un'istanza di una classe C# generica usando un tipo di valore o un parametro di tipo riferimento, la reflection può eseguire una query in fase di esecuzione ed è possibile verificare sia il tipo effettivo che il relativo parametro di tipo.

Vedi anche