2016-04-13 11 views
7

Il seguente frammento di codice viene estratto dal Esempio 10-11 (p 343). Da di programmazione C# 5.0:Perché il codice generico dovrebbe compilare senza vincolare T?

public static T[] Select<T>(this CultureInfo[] cultures, 
          Func<CultureInfo, T> map) 
{ 
    var result = new T[cultures.Length]; 
    for (int i = 0; i < cultures.Length; ++i) 
    { 
     result[i] = map(cultures[i]); 
    } 
    return result; 
} 

io non riesco a capire come si potrebbe ottenere compilato senza esporre alcuna informazione su T da applicando i vincoli su di esso. In particolare, in che modo il compilatore può sapere quanti byte allocare per l'array, dato che T potrebbe non essere un tipo di riferimento, ma un tipo di valore (ad esempio, struct)? Inoltre, la semantica dell'attività di assegnazione result[i] = map(cultures[i]) sembra dipendere dal fatto che T sia un tipo di riferimento o un tipo di valore.

+0

"quanti byte allocare per l'array" Il compilatore deve saperlo in anticipo? – BoltClock

+0

Non sono sicuro di aver capito correttamente la domanda, ma penso che il compilatore JIT sappia tutto quello che dici –

risposta

3

Penso che la chiave qui per capire questo è meglio espresso come segue:

In primo luogo, al momento della compilazione, viene creato un metodo generico, che è semplicemente la versione IL di quello che hai scritto lì nell'origine . È typesable (map restituisce un T e le celle di matrice sono di tipo T, quindi non c'è alcun problema lì) e tutto è a posto.

Ora, in fase di esecuzione, (concettualmente almeno) la prima volta che si utilizza Select<string>, ad esempio, il compilatore JIT a quel punto crea un nuovo metodo.Chiamiamo questo metodo Select__string (in realtà è in genere chiamato qualcosa come Select'string Penso, ma non voglio che tu pensi che sia importante ai fini di questa spiegazione). In questo nuovo metodo, tutte le istanze di T vengono sostituiti con string, e così, naturalmente, in quel metodo compilato tutto è facilmente elaborato - assegnazioni, l'allocazione dimensione della matrice, ecc

Successiva Ti Select<int> da qualche altra parte. Il compilatore JIT ora crea un metodo completamente nuovo che chiameremo Select__int. E ancora, tutte le istanze di T vengono sostituite con int, e così di nuovo, la semantica della dimensione degli array e dell'assegnazione vengono gestite facilmente.

Lo stesso vale per i tipi generici. Quando vengono effettivamente utilizzati, List<string> e List<int> sono due tipi completamente separati. Questo è il motivo per cui i generici .net sono così facili da scrivere e usare.

Se questo non è chiaro, puoi dare un esempio di cosa specificamente, nel tuo codice sopra, pensi che avrebbe ancora bisogno di essere conosciuto o limitato in fase di compilazione?

+0

Grazie per la spiegazione dettagliata. Mi sembra di avere qualche idea ora. Solo quando fai qualcosa che non si applica a tutti i tipi, ad esempio invocando un metodo specializzato, avrai mai bisogno di applicare alcun vincolo. – Lingxi

+0

Sì, esattamente giusto. –

9

Non esiste alcuna dipendenza tra CultureInfo e T di sorta. Tutti gli T sono ugualmente validi, dal momento che gestisci la "conversione" utilizzando il tuo Func<CultureInfo, T> personalizzato.

Penso che tu sia confuso su come i generici funzionano in C#. Non sono una funzionalità di compilazione (come in Java), sopravvivono fino al runtime. Solo quando hai effettivamente bisogno di un tipo generico reificato è quel tipo compilato, ea quel punto sa già cosa sia T.

Questo è ovviamente uno dei motivi per cui C# non ha Java List<?>. Non esiste un "tipo generico comune", si sta solo "ritardando" la reificazione dei tipi - una volta che si dispone di List<string> e List<int>, i due sono tipi completamente separati, e solo la riflessione ti dice che provengono dallo stesso tipo generico.

+0

Tuttavia, se invochi un qualsiasi metodo di 'T', è necessario applicare un vincolo. Quindi non tutte le cose possono essere rimandate al runtime. Esiste una regola semplice e generale che deve essere determinata al momento della compilazione e quale no? Scusa se la domanda sembra sciocca. Sono davvero nuovo di C#: P Or, forse questo merita una nuova domanda in sé. – Lingxi

+2

@Lingxi: Sì, * solo * quando si inizia a richiamare membri di T che dipendono dalla conoscenza di cosa T è esattamente o quando si iniziano ad assegnare valori che dipendono dal fatto che T sia un valore o un tipo di riferimento. Se non fai nessuno di questi nel tuo metodo generico, allora non importa quale sia T. – BoltClock

+0

@BoltClock Grazie per aver convalidato la mia ipotesi, e sono davvero felice che la regola sia così semplice :) – Lingxi

4

In particolare, come potrebbe il compilatore sapere quanti byte da allocare per la matrice,

non lo fa. Non c'è bisogno che il compilatore lo sappia.

Ma supponiamo che l'istruzione newarr abbia bisogno di avere una dimensione in byte. Quindi il compilatore potrebbe semplicemente lasciare che la moltiplicazione della dimensione del tipo di elemento e la lunghezza dell'array vengano eseguite in fase di esecuzione.

Inoltre, la semantica dell'operazione di assegnazione result[i] = map(cultures[i]) sembra dipendere dal fatto T è un tipo di riferimento o un tipo di valore.

Non dipende da quello. Indipendentemente dal tipo, viene eseguita una copia esatta di qualsiasi cosa restituisca lo map. Se si tratta di un tipo di valore, significa che il valore viene copiato. Se si tratta di un tipo di riferimento, significa che il riferimento viene copiato.

+0

Quindi, l'unica cosa che deve essere determinata in fase di compilazione (e quindi deve essere applicato un vincolo) è quando si invoca un metodo di 'T'? – Lingxi

Problemi correlati