2013-10-16 16 views
72

This question mi ha fatto riflettere su dove l'implementazione concreta di un metodo generico viene effettivamente all'esistenza. Ho provato il google, ma non mi viene in mente la ricerca giusta.Come, quando e dove i metodi generici sono resi concreti?

Se prendiamo questo semplice esempio:

class Program 
{ 
    public static T GetDefault<T>() 
    { 
     return default(T); 
    } 

    static void Main(string[] args) 
    { 
     int i = GetDefault<int>(); 
     double d = GetDefault<double>(); 
     string s = GetDefault<string>(); 
    } 
} 

nella mia testa ho sempre pensato che a un certo punto si traduce in un'implementazione con le 3 necessarie implementazioni concrete in modo tale che, nel ingenua pseudo mangling, abbiamo sarebbe questo implementaiton concreta logica in cui i tipi specifici risultato utilizzati nelle allocazioni dello stack corretti ecc

class Program 
{ 
    static void Main(string[] args) 
    { 
     int i = GetDefaultSystemInt32(); 
     double d = GetDefaultSystemFloat64(); 
     string s = GetDefaultSystemString(); 
    } 

    static int GetDefaultSystemInt32() 
    { 
     int i = 0; 
     return i; 
    } 
    static double GetDefaultSystemFloat64() 
    { 
     double d = 0.0; 
     return d; 
    } 
    static string GetDefaultSystemString() 
    { 
     string s = null; 
     return s; 
    } 
} 

Guardando il iL per il programma generico è ancora espresso in termini di tipi generici:

.method public hidebysig static !!T GetDefault<T>() cil managed 
{ 
    // Code size  15 (0xf) 
    .maxstack 1 
    .locals init ([0] !!T CS$1$0000, 
      [1] !!T CS$0$0001) 
    IL_0000: nop 
    IL_0001: ldloca.s CS$0$0001 
    IL_0003: initobj !!T 
    IL_0009: ldloc.1 
    IL_000a: stloc.0 
    IL_000b: br.s  IL_000d 
    IL_000d: ldloc.0 
    IL_000e: ret 
} // end of method Program::GetDefault 

Quindi come ea che punto è deciso che un int, e quindi un double e quindi una stringa devono essere allocati nello stack e restituiti al chiamante? Questa è un'operazione del processo JIT? Sto guardando questo nella luce completamente sbagliata?

+5

Come me, sembra che tu stia pensando a questo in termini C++. Non conosco la risposta, ma ricordo di aver letto alcuni fatti inaspettati sui generici in C#. –

+4

Stai cercando bene, IL supporta i generici. Il grande progresso è che le classi negli assembly già compilati supportano ancora i generici. _ (come l'intero framework .NET) _ –

+0

@Jonathan Wood Guardando tutto questo dal punto di vista di come i nomi dei metodi del mangano C++! – dkackman

risposta

77

In C#, i concetti di tipi e metodi generici sono supportati dal runtime stesso. Il compilatore C# non ha bisogno di creare effettivamente una versione concreta di un metodo generico.

L'effettivo metodo "concreto" generico viene creato in fase di esecuzione dal JIT e non esiste nell'IL. La prima volta che viene utilizzato un metodo generico con un tipo, il JIT vedrà se è stato creato e, in caso contrario, costruirà il metodo appropriato per quel tipo generico.

Questa è una delle differenze fondamentali tra generici e cose come i modelli in C++. È anche la ragione principale di molte limitazioni con i generici - dal momento che il compilatore non sta effettivamente creando l'implementazione runtime per i tipi, le restrizioni dell'interfaccia sono gestite da vincoli di tempo di compilazione, che rendono i farmaci generici un po 'più limitanti rispetto ai template in C++ in termini di potenziali casi d'uso. Tuttavia, il fatto che siano supportati nel runtime stesso consente la creazione di tipi generici e l'utilizzo dalle librerie possibili in modi che non sono supportati in C++ e altre implementazioni di template create in fase di compilazione.

+1

In effetti, una differenza qualitativa tra i modelli C++ e i generici C# è che se non fosse per i vincoli di tempo e memoria, sarebbe possibile per un file eseguibile relativamente piccolo produrre istanze di un numero illimitato di tipi riconoscibili differenti (ad esempio un programma potrebbe prendere una stringa di input di lunghezza arbitraria (ad es. "FRED") e creare un'istanza di tipo 'F >>'. – supercat

+2

Buona risposta Reed Qualsiasi materiale di riferimento che descriva ciò che si conosce? – dkackman

+7

@dkackman Ho letto http://www.artima.com/intv/generics.html Si discute qui. –

45

Il codice macchina effettivo per un metodo generico viene creato, come sempre, quando viene eseguito il metodo. A quel punto, il jitter controlla prima se un candidato adatto è stato battuto prima. Il che è molto comune, il codice per un metodo il cui tipo di runtime concreto T è un tipo di riferimento deve essere generato una sola volta ed è adatto per ogni possibile tipo di riferimento T. I vincoli su T assicurano che questo codice macchina sia sempre valido, precedentemente controllato dal compilatore C#.

È possibile generare copie aggiuntive per T che sono tipi di valore, il loro codice macchina è diverso perché i valori T non sono più semplici puntatori.

Quindi sì, nel tuo caso finirai con tre metodi distinti. La versione <string> sarebbe utilizzabile per qualsiasi tipo di riferimento ma non ne hai altri. E le versioni <int> e <double> si adattano alla categoria "T's che sono tipi di valore".

In caso contrario, un eccellente esempio, i valori di ritorno di questi metodi vengono restituiti al chiamante in modo diverso. Sul jitter x64, la versione di stringa restituisce il valore con il registro RAX, come qualsiasi valore di puntatore restituito, la versione int restituisce con il registro EAX, la versione doppia ritorna con il registro XMM0.