Je vais ajouter ma voix au bruit et essayer de clarifier les choses:
Les génériques C # vous permettent de déclarer quelque chose comme ça.
List<Person> foo = new List<Person>();
puis le compilateur vous empêchera de mettre des choses qui ne sont pas Person
dans la liste.
Dans les coulisses, le compilateur C # met juste List<Person>
dans le fichier dll .NET, mais au moment de l'exécution, le compilateur JIT va et construit un nouvel ensemble de code, comme si vous aviez écrit une classe de liste spéciale juste pour contenir des personnes - quelque chose comme ListOfPerson
.
L'avantage de cela est que cela le rend très rapide. Il n'y a pas de casting ou tout autre truc, et parce que la dll contient les informations dont il s'agit d'une liste Person
, un autre code qui l'examine plus tard en utilisant la réflexion peut dire qu'il contient des Person
objets (vous obtenez donc intellisense, etc.).
L'inconvénient est que l'ancien code C # 1.0 et 1.1 (avant d'ajouter des génériques) ne comprend pas ces nouveaux List<something>
, vous devez donc reconvertir manuellement les choses en vieux List
pour les interagir avec elles. Ce n'est pas un gros problème, car le code binaire C # 2.0 n'est pas rétrocompatible. La seule fois où cela se produira, c'est si vous mettez à niveau un ancien code C # 1.0 / 1.1 vers C # 2.0
Les génériques Java vous permettent de déclarer quelque chose comme ça.
ArrayList<Person> foo = new ArrayList<Person>();
En surface, il a la même apparence, et il l'est en quelque sorte. Le compilateur vous empêchera également de mettre des choses qui ne sont pas Person
dans la liste.
La différence est ce qui se passe dans les coulisses. Contrairement à C #, Java ne va pas créer un spécial ListOfPerson
- il utilise simplement l'ancien simple ArrayList
qui a toujours été en Java. Lorsque vous sortez les choses du tableau, la Person p = (Person)foo.get(1);
danse de casting habituelle doit encore être faite. Le compilateur vous permet d'économiser les pressions sur les touches, mais la vitesse de frappe / casting est toujours engagée comme elle l'a toujours été.
Quand les gens mentionnent "Type Erasure", c'est de cela qu'ils parlent. Le compilateur insère les transtypages pour vous, puis «efface» le fait qu'il est censé être une liste de Person
non seulementObject
L'avantage de cette approche est que l'ancien code qui ne comprend pas les génériques n'a pas à s'en soucier. Il s'agit toujours du même vieux ArrayList
qu'il l'a toujours fait. Ceci est plus important dans le monde java car ils voulaient prendre en charge la compilation de code à l'aide de Java 5 avec des génériques, et le faire fonctionner sur les anciennes JVM 1.4 ou précédentes, avec lesquelles Microsoft a délibérément décidé de ne pas s'embêter.
L'inconvénient est le coup de vitesse que j'ai mentionné précédemment, et aussi parce qu'il n'y a pas de ListOfPerson
pseudo-classe ou quelque chose comme ça dans les fichiers .class, du code qui le regarde plus tard (avec réflexion, ou si vous le retirez d'une autre collection où il a été converti Object
ou ainsi de suite) ne peut en aucune façon dire qu'il est censé être une liste contenant uniquement Person
et pas simplement une autre liste de tableaux.
Les modèles C ++ vous permettent de déclarer quelque chose comme ça
std::list<Person>* foo = new std::list<Person>();
Cela ressemble à des génériques C # et Java, et il fera ce que vous pensez qu'il devrait faire, mais en coulisses, des choses différentes se produisent.
Il a le plus en commun avec les génériques C # dans la mesure où il est spécial pseudo-classes
plutôt que de simplement jeter les informations de type comme Java, mais c'est une toute autre marmite de poisson.
C # et Java produisent tous deux une sortie conçue pour les machines virtuelles. Si vous écrivez du code qui contient une Person
classe, dans les deux cas, certaines informations sur une Person
classe iront dans le fichier .dll ou .class, et la JVM / CLR s'en chargera.
C ++ produit du code binaire x86 brut. Tout n'est pas un objet et aucune machine virtuelle sous-jacente n'a besoin de connaître une Person
classe. Il n'y a pas de boxe ou de déballage, et les fonctions n'ont pas à appartenir à des classes, ni à rien d'autre.
Pour cette raison, le compilateur C ++ n'impose aucune restriction sur ce que vous pouvez faire avec les modèles - essentiellement tout code que vous pourriez écrire manuellement, vous pouvez obtenir des modèles à écrire pour vous.
L'exemple le plus évident est d'ajouter des choses:
En C # et Java, le système générique doit savoir quelles méthodes sont disponibles pour une classe, et il doit le transmettre à la machine virtuelle. La seule façon de le dire est de coder en dur la classe réelle ou d'utiliser des interfaces. Par exemple:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Ce code ne sera pas compilé en C # ou Java, car il ne sait pas que le type T
fournit réellement une méthode appelée Name (). Vous devez le dire - en C # comme ceci:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
Et puis vous devez vous assurer que les choses que vous transmettez à addNames implémentent l'interface IHasName et ainsi de suite. La syntaxe java est différente ( <T extends IHasName>
), mais elle souffre des mêmes problèmes.
Le cas «classique» de ce problème essaie d'écrire une fonction qui fait cela
string addNames<T>( T first, T second ) { return first + second; }
Vous ne pouvez pas réellement écrire ce code car il n'y a aucun moyen de déclarer une interface avec la +
méthode qu'il contient. Vous échouez.
C ++ ne souffre d'aucun de ces problèmes. Le compilateur ne se soucie pas de transmettre des types à n'importe quelle machine virtuelle - si vos deux objets ont une fonction .Name (), il se compilera. S'ils ne le font pas, ce ne sera pas le cas. Facile.
Alors voilà :-)