Aggiungerò la mia voce al rumore e cercherò di chiarire le cose:
I generici C # ti consentono di dichiarare qualcosa di simile.
List<Person> foo = new List<Person>();
e poi il compilatore ti impedirà di inserire cose che non sono Person
nell'elenco.
Dietro le quinte il compilatore C # sta solo inserendo List<Person>
il file .NET dll, ma in fase di esecuzione il compilatore JIT va e costruisce un nuovo set di codice, come se avessi scritto una classe di elenco speciale solo per contenere persone, qualcosa di simileListOfPerson
.
Il vantaggio di questo è che lo rende davvero veloce. Non ci sono casting o altre cose, e poiché la dll contiene le informazioni che si tratta di un elenco Person
, altro codice che lo esamina in seguito utilizzando la riflessione può dire che contienePerson
oggetti (quindi ottieni intellisense e così via).
Lo svantaggio di questo è che il vecchio codice C # 1.0 e 1.1 (prima che aggiungessero i generici) non comprende questi nuovi List<something>
, quindi devi riconvertire manualmente le cose al vecchioList
per interagire con loro. Questo non è un grosso problema, perché il codice binario C # 2.0 non è compatibile con le versioni precedenti. L'unica volta che ciò accadrà è se stai aggiornando un vecchio codice C # 1.0 / 1.1 a C # 2.0
Java Generics ti consente di dichiarare qualcosa di simile.
ArrayList<Person> foo = new ArrayList<Person>();
In superficie sembra lo stesso, e più o meno lo è. Il compilatore ti impedirà anche di inserire cose che non sono Person
nell'elenco.
La differenza è cosa succede dietro le quinte. A differenza di C #, Java non crea uno speciale ListOfPerson
- usa solo il vecchio semplice ArrayList
che è sempre stato in Java. Quando togli le cose dallo schieramento, il solito Person p = (Person)foo.get(1);
ballo di casting deve ancora essere fatto. Il compilatore ti sta risparmiando le pressioni dei tasti, ma la velocità di hit / casting è ancora sostenuta come sempre.
Quando le persone menzionano "Type Erasure" questo è ciò di cui stanno parlando. Il compilatore inserisce i cast per te, quindi "cancella" il fatto che deve essere un elenco Person
non solo diObject
Il vantaggio di questo approccio è che il vecchio codice che non comprende i generici non deve preoccuparsene. Ha ancora a che fare con lo stesso vecchio ArrayList
come ha sempre fatto. Questo è più importante nel mondo java perché volevano supportare la compilazione di codice utilizzando Java 5 con generics e farlo funzionare su vecchie 1.4 o JVM precedenti, con cui Microsoft ha deliberatamente deciso di non preoccuparsi.
Lo svantaggio è il colpo di velocità che ho citato in precedenza, e anche perché non c'è nessuna ListOfPerson
pseudo-classe o qualcosa del genere nei file .class, codice che lo guarda in seguito (con riflessione o se lo estrai da un'altra raccolta dove è stato convertito Object
o così via) non può dire in alcun modo che deve essere un elenco contenente solo Person
e non solo qualsiasi altro elenco di array.
I modelli C ++ ti consentono di dichiarare qualcosa di simile
std::list<Person>* foo = new std::list<Person>();
Sembra generici C # e Java, e farà quello che pensi che dovrebbe fare, ma dietro le quinte stanno accadendo cose diverse.
Ha più in comune con i generici di C # in quanto costruisce speciali pseudo-classes
piuttosto che buttare via le informazioni sul tipo come fa java, ma è una cosa completamente diversa.
Sia C # che Java producono un output progettato per le macchine virtuali. Se scrivi del codice che contiene una Person
classe, in entrambi i casi alcune informazioni su una Person
classe andranno nel file .dll o .class, e JVM / CLR farà qualcosa con questo.
C ++ produce codice binario x86 grezzo. Tutto è non è un oggetto, e non c'è macchina virtuale sottostante che ha bisogno di sapere di una Person
classe. Non c'è boxing o unboxing e le funzioni non devono appartenere a classi, o addirittura niente.
Per questo motivo, il compilatore C ++ non pone restrizioni su ciò che puoi fare con i modelli: praticamente qualsiasi codice che potresti scrivere manualmente, puoi ottenere modelli da scrivere per te.
L'esempio più ovvio è l'aggiunta di cose:
In C # e Java, il sistema generics deve sapere quali metodi sono disponibili per una classe e deve trasmetterli alla macchina virtuale. L'unico modo per dirlo è codificare la classe effettiva in o utilizzare le interfacce. Per esempio:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Quel codice non verrà compilato in C # o Java, perché non sa che il tipo T
fornisce effettivamente un metodo chiamato Name (). Devi dirlo - in C # come questo:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
E poi devi assicurarti che le cose che passi a addNames implementino l'interfaccia IHasName e così via. La sintassi java è diversa ( <T extends IHasName>
), ma soffre degli stessi problemi.
Il caso "classico" di questo problema è cercare di scrivere una funzione che lo faccia
string addNames<T>( T first, T second ) { return first + second; }
Non puoi effettivamente scrivere questo codice perché non ci sono modi per dichiarare un'interfaccia con il +
metodo al suo interno. Hai fallito.
Il C ++ non soffre di nessuno di questi problemi. Al compilatore non interessa passare i tipi a nessuna macchina virtuale: se entrambi gli oggetti hanno una funzione .Name (), verrà compilato. Se non lo fanno, non lo faranno. Semplice.
Così il gioco è fatto :-)