2012-07-11 20 views
10

Avevo pensato che Generics in C# fosse implementato in modo tale che una nuova classe/metodo/what-have-tu sia stato generato, sia in fase di esecuzione che in fase di compilazione, quando è stato utilizzato un nuovo tipo generico, simile ai modelli C++ (che non ho mai esaminato e posso benissimo sbagliare, su cui accetterei volentieri la correzione).Come vengono implementati i C# Generics?

Ma nel mio codice mi si avvicinò con un controesempio precisa:

static class Program { 
    static void Main() 
    { 
     Test testVar = new Test(); 

     GenericTest<Test> genericTest = new GenericTest<Test>(); 
     int gen = genericTest.Get(testVar); 

     RegularTest regTest = new RegularTest(); 
     int reg = regTest.Get(testVar); 

     if (gen == ((object)testVar).GetHashCode()) 
     { 
      Console.WriteLine("Got Object's hashcode from GenericTest!"); 
     } 
     if (reg == testVar.GetHashCode()) 
     { 
      Console.WriteLine("Got Test's hashcode from RegularTest!"); 
     } 
    } 

    class Test 
    { 
     public new int GetHashCode() 
     { 
      return 0; 
     } 
    } 

    class GenericTest<T> 
    { 
     public int Get(T obj) 
     { 
      return obj.GetHashCode(); 
     } 
    } 

    class RegularTest 
    { 
     public int Get(Test obj) 
     { 
      return obj.GetHashCode(); 
     } 
    } 
} 

Entrambe queste linee di console di stampa.

So che il vero motivo per cui questo accade è che la chiamata virtuale a Object.GetHashCode() non si risolve in Test.GetHashCode() perché il metodo in Test è contrassegnato come nuovo piuttosto che come override. Pertanto, so se ho usato "override" piuttosto che "new" su Test.GetHashCode(), quindi il ritorno di 0 avrebbe sostituito in modo polimorfico il metodo GetHashCode in oggetto e questo non sarebbe vero, ma secondo la mia (precedente) comprensione di generici C# non avrebbe avuto importanza perché ogni istanza di T sarebbe stata sostituita con Test, e quindi la chiamata al metodo avrebbe statisticamente (o con il tempo di risoluzione generico) risolta nel "nuovo" metodo.

Quindi la mia domanda è questa: Come vengono implementati i generici in C#? Non conosco il bytecode CIL, ma conosco il bytecode Java, quindi capisco come i linguaggi CLI orientati agli oggetti funzionino a un livello basso. Sentiti libero di spiegare a quel livello.

Per inciso, ho pensato che i generici C# fossero implementati in quel modo perché tutti chiamano sempre il sistema generico in C# "True Generics", rispetto al sistema di cancellazione del tipo di Java.

+0

qualsiasi motivo per trasmettere all'oggetto qui 'gen == ((oggetto) testVar) .GetHashCode()'? – AlwaysAProgrammer

+0

Anche se non risponde direttamente alla tua domanda, http://blogs.msdn.com/b/ericlippert/archive/2012/07/10/when-is-a-cast-not-a-cast.aspx ha alcune buone informazioni su come vengono espressi i farmaci generici e su come si relazionano tra loro in C#. – devstruck

+0

@Yogendra Facendo questo accede al metodo Object.GetHashCode() piuttosto che al metodo "nuovo" Test.GetHashCode(). Ecco perché restituisce un valore diverso (perché esegue un metodo completamente diverso). – Carrotman42

risposta

7

In GenericTest<T>.Get(T), il compilatore C# ha già scelto che object.GetHashCode deve essere chiamato (virtualmente). In nessun modo questo si risolverà con il "nuovo" metodo GetHashCode in fase di esecuzione (che avrà il proprio slot nella tabella dei metodi, piuttosto che sovrascrivere lo slot per object.GetHashCode).

Da Eric Lippert di What's the difference, part one: Generics are not templates, la questione si spiega (la configurazione utilizzata è leggermente diverso, ma le lezioni si traducono bene per lo scenario):

Questo dimostra che i generici in C# non sono come i modelli in C++. Puoi pensare ai modelli come meccanismo di ricerca e sostituzione di fancy-pants . [...] Non è così che funzionano i tipi generici; i tipi generici sono, bene, generico. Facciamo la risoluzione di sovraccarico una volta e cuociamo nel risultato . [...] L'IL che abbiamo generato per il tipo generico ha già il metodo da selezionare. Il jitter non dice "beh, so per certo che se avessimo chiesto al compilatore C# di eseguire adesso con queste informazioni aggiuntive, sarebbe stato scelto un sovraccarico diverso da . Permettimi di riscrivere il codice generato per ignorare il codice che il compilatore C# ha originariamente generato ... "Il jitter conosce lo niente riguardo le regole di C#.

E una soluzione per i tuoi semantica desiderati:

Ora, se si vuole la risoluzione di sovraccarico deve essere nuovamente eseguito in fase di esecuzione in base ai tipi di runtime di gli argomenti, possiamo farlo per tu; questo è ciò che fa la nuova funzione "dinamica" in C# 4.0. Basta sostituire "oggetto" con "dinamico" e quando si effettua una chiamata che coinvolge quell'oggetto, eseguiremo l'algoritmo di risoluzione sovraccarico in fase di runtime e sputeremo dinamicamente il codice che chiama il metodo che il compilatore avrebbe scelto, se fosse noto tutti i tipi di runtime in fase di compilazione.

+3

Ah Eric, qualunque cosa faremmo senza di te. – Servy

+0

Sì, l'ho detto nel paragrafo che inizia con "So che la vera ragione per cui questo accade è ..." La mia domanda era "Come vengono implementati i C# Generics?" Leggerò il collegamento che hai inviato anche se forse risponderà alla mia domanda. – Carrotman42

+0

@ la tua modifica: non è che voglio farlo: vengo da uno sfondo di Java in cui non esiste questo calvario del "nuovo metodo". Penso che sia piuttosto incline agli errori degli utenti e non ho intenzione di usarlo apposta. Il motivo per cui mi sono imbattuto in questo era perché stavo scrivendo una classe astratta in cui volevo forzare le sottoclassi per implementare GetHashCode, così ho scritto "abstract pubblico int GetHashcode();", non rendendomi conto che per farlo funzionare con qualcosa chiamando genericamente GetHashCode dovevo effettivamente dire "public override abstract int GetHashcode();", che è terribilmente prolisso per i miei gusti. – Carrotman42

Problemi correlati