Come vengono implementati i generici?


16

Questa è la domanda dal punto di vista degli interni del compilatore.

Sono interessato ai generici, non ai modelli (C ++), quindi ho contrassegnato la domanda con C #. Non Java, perché AFAIK i generici in entrambe le lingue differiscono nelle implementazioni.

Quando guardo le lingue senza generici è abbastanza semplice, puoi convalidare la definizione della classe, aggiungerla alla gerarchia e il gioco è fatto.

Ma cosa fare con la classe generica e, soprattutto, come gestire i riferimenti ad essa? Come assicurarsi che i campi statici siano singolari per istanze (cioè ogni volta che vengono risolti parametri generici).

Diciamo che vedo una chiamata:

var x = new Foo<Bar>();

Aggiungo una nuova Foo_Barclasse alla gerarchia?


Aggiornamento: finora ho trovato solo 2 post pertinenti, anche se non entrano in molti dettagli in senso "come farlo da soli":


Voto perché penso che una risposta completa sarebbe interessante. Ho alcune idee su come funziona ma non abbastanza per rispondere con precisione. Non penso che i generici in C # si compilino in classi specializzate per ogni tipo generico. Sembrano essere risolti in fase di esecuzione (può esserci un notevole aumento della velocità dall'uso dei generici). Forse possiamo convincere Eric Lippert a suonare?
KChaloux,

2
@KChaloux: a livello di MSIL, c'è una descrizione del generico. Quando viene eseguito JIT, crea un codice macchina separato per ciascun tipo di valore utilizzato come parametri generici e un altro set di codice macchina che copre tutti i tipi di riferimento. Conservare la descrizione generica in MSIL è davvero bello perché ti consente di creare nuove istanze in fase di esecuzione.
Ben Voigt,

@Ben Ecco perché non ho provato a rispondere effettivamente alla domanda: p
KChaloux,

Non sono sicuro se si è ancora in giro, ma che lingua Sei la compilazione a . Ciò avrà molta influenza su come implementare i farmaci generici. Posso fornire informazioni su come di solito mi sono avvicinato al front-end, ma il back-end può variare notevolmente.
Telastyn,

@Telastyn, per quegli argomenti sono sicuro di :-) Sto cercando qualcosa di molto vicino a C #, nel mio caso sto compilando per PHP (non scherzo). Ti sarò grato se condividi le tue conoscenze.
Greenoldman,

Risposte:


4

Come assicurarsi che i campi statici siano singolari per istanze (cioè ogni volta che vengono risolti parametri generici).

Ogni istanza generica ha una propria copia della MethodTable (dal nome confuso), che è dove sono memorizzati i campi statici.

Diciamo che vedo una chiamata:

var x = new Foo<Bar>();

Aggiungo una nuova Foo_Barclasse alla gerarchia?

Non sono sicuro che sia utile pensare alla gerarchia di classi come a una struttura che esiste effettivamente in fase di esecuzione, è più un costrutto logico.

Ma se consideri MethodTables, ognuno con un puntatore indiretto alla sua classe base, per formare questa gerarchia, allora sì, questo aggiunge una nuova classe alla gerarchia.


Grazie, questo è un pezzo interessante. Quindi i campi statici sono risolti in modo simile alla tabella virtuale, giusto? C'è un riferimento al dizionario "globale" che contiene voci per ciascun tipo? Quindi potrei avere 2 assiemi che non si conoscono Foo<string>e che non genereranno due istanze di campo statico Foo.
Greenoldman,

1
@greenoldman Beh, non in modo simile alla tabella virtuale, esattamente lo stesso. La MethodTable contiene sia campi statici che riferimenti a metodi del tipo, utilizzati nella spedizione virtuale (ecco perché si chiama MethodTable). E sì, il CLR deve avere una tabella che può usare per accedere a tutte le tabelle dei metodi.
svick,

2

Vedo due domande concrete concrete lì dentro. Probabilmente vorrai porre ulteriori domande correlate (come domanda separata con un link indietro a questo) per ottenere una piena comprensione.

In che modo ai campi statici vengono fornite istanze separate per istanza generica?

Bene, per i membri statici che non sono correlati ai parametri di tipo generico, questo è piuttosto semplice (usa un dizionario mappato dai parametri generici al valore).

I membri (statici o meno) correlati ai parametri del tipo possono essere gestiti tramite la cancellazione del tipo. Basta usare qualunque sia il vincolo più forte (spesso System.Object). Poiché le informazioni sul tipo vengono cancellate dopo i controlli del tipo di compilatore, significa che i controlli del tipo di runtime non saranno necessari (sebbene i cast di interfaccia possano ancora esistere in fase di runtime).

Ogni istanza generica viene visualizzata separatamente nella gerarchia dei tipi?

Non in generici .NET. È stata presa la decisione di escludere l'ereditarietà dai parametri di tipo, quindi risulta che tutte le istanze di un generico occupano lo stesso punto nella gerarchia dei tipi.

Questa è stata probabilmente una buona decisione, perché la mancata ricerca di nomi da una classe base sarebbe incredibilmente sorprendente.


Il mio problema è che non riesco a smettere di pensare in termini di modello. Ad esempio - a differenza del modello, la classe generica è completamente compilata. Ciò significa che in altri assembly che utilizzano questa classe cosa succede? Il metodo già compilato viene chiamato con casting interno? Dubito che i generici possano fare affidamento sul vincolo - piuttosto sull'argomento, altrimenti Foo<int>e Foo<string>colpirebbero gli stessi dati Foosenza vincoli.
Greenoldman,

1
@greenoldman: possiamo evitare i tipi di valore per un minuto, perché in realtà sono gestiti in modo speciale? Se hai List<string>e List<Form>, dal momento che List<T>internamente ha un membro di tipo T[]e non ci sono vincoli T, ciò che otterrai effettivamente è il codice macchina che manipola un object[]. Tuttavia, poiché solo le Tistanze vengono inserite nell'array, tutto ciò che esce può essere restituito come Tsenza un controllo aggiuntivo del tipo. D'altra parte, se avessi ControlCollection<T> where T : Control, l'array interno T[]diventerebbe Control[].
Ben Voigt,

Comprendo correttamente che il vincolo viene preso e utilizzato come nome di tipo interno, ma quando viene effettivamente utilizzata la classe viene utilizzato il casting? OK, ho capito quel modello, ma avevo l'impressione che Java lo usasse, non C #.
Greenoldman,

3
@greenoldman: Java esegue la cancellazione del tipo nel passaggio di traduzione source bytecode. Il che rende impossibile per il verificatore verificare il codice generico. C # lo fa nel passaggio bytecode-> codice macchina.
Ben Voigt,

@BenVoigt Alcune informazioni vengono conservate in Java sui tipi generici, altrimenti non è possibile compilare contro una classe che utilizza un generico senza la sua fonte. Non è semplicemente tenuto nella sequenza di bytecode stessa AIUI, ma piuttosto nei metadati di classe.
Donal Fellows,

1

Ma cosa fare con la classe generica e, soprattutto, come gestire i riferimenti ad essa?

Il modo generale nel front-end del compilatore è di avere due tipi di istanze di tipo, il tipo generico ( List<T>) e un tipo generico associato ( List<Foo>). Il tipo generico definisce quali funzioni esistono, quali campi e ha riferimenti di tipo generico ovunque vengano Tutilizzati. Il tipo generico associato contiene un riferimento al tipo generico e una serie di argomenti di tipo. Che ha abbastanza informazioni per generare quindi un tipo concreto, sostituendo i riferimenti di tipo generico con Fooo qualunque siano gli argomenti di tipo. Questo tipo di distinzione è importante quando stai facendo l'inferenza del tipo e devi inferire List<T>contro List<Foo>.

Invece di pensare a generici come modelli (che costruiscono direttamente varie implementazioni), può essere utile invece pensarli come costruttori di tipi di linguaggio funzionale (dove gli argomenti generici sono come argomenti in una funzione che ti dà un tipo).

Per quanto riguarda il back-end, non lo so davvero. Tutto il mio lavoro con i generici ha preso di mira CIL come backend, così da poterli compilare in generici supportati lì.


Grazie mille (peccato che non possa accettare risposte multiple). È bello sapere che ho fatto praticamente quel passo in modo corretto - nel mio caso List<T>contiene il tipo reale (la sua definizione), mentre List<Foo>(grazie anche per il pezzo terminologico) con il mio approccio mantengo le dichiarazioni di List<T>(ovviamente ora legate a Fooinvece di T).
Greenoldman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.