Je vais ajouter ma voix au bruit et tenter de rendre les choses plus claires :
Les génériques de C# vous permettent de déclarer quelque chose comme ceci.
List<Person> foo = new List<Person>();
et alors le compilateur vous empêchera de mettre des choses qui ne sont pas Person
dans la liste.
Dans les coulisses, le compilateur C# met simplement List<Person>
dans le fichier .NET dll, mais au moment de l'exécution, le compilateur JIT construit un nouvel ensemble de code, comme si vous aviez écrit une classe de liste spéciale pour contenir les personnes - quelque chose comme ListOfPerson
.
L'avantage de cette méthode est qu'elle est très rapide. Il n'y a pas de casting ou autre, et parce que la dll contient l'information qu'il s'agit d'une Liste de Person
un autre code qui le regarde plus tard en utilisant la réflexion peut dire qu'il contient Person
(afin de bénéficier de l'intellisense, etc.).
L'inconvénient est que l'ancien code C# 1.0 et 1.1 (avant l'ajout des génériques) ne comprend pas ces nouvelles fonctionnalités. List<something>
Vous devez donc reconvertir manuellement les choses vers l'ancien format. List
pour interopérer avec eux. Ce n'est pas un gros problème, car le code binaire de C# 2.0 n'est pas rétrocompatible. La seule fois où cela se produira est si vous mettez à niveau un vieux code C# 1.0/1.1 vers C# 2.0.
Les génériques Java vous permettent de déclarer quelque chose comme ceci.
ArrayList<Person> foo = new ArrayList<Person>();
En apparence, c'est la même chose, et c'est un peu le cas. Le compilateur vous empêchera aussi 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 construit pas un code spécial ListOfPerson
- il utilise simplement le bon vieux ArrayList
qui a toujours été en Java. Quand vous sortez des choses du tableau, les habituels Person p = (Person)foo.get(1);
La danse du casting doit encore être faite. Le compilateur vous évite d'appuyer sur les touches, mais la vitesse d'exécution est toujours la même.
Quand les gens mentionnent "l'effacement de type", c'est de cela qu'ils parlent. Le compilateur insère les casts pour vous, puis "efface" le fait que ce soit censé être une liste de Person
pas seulement Object
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 de la même vieille ArrayList
comme il l'a toujours fait. C'est plus important dans le monde Java parce qu'ils voulaient prendre en charge la compilation de code utilisant Java 5 avec les génériques, et le faire fonctionner sur les anciennes JVM 1.4 ou antérieures, ce que Microsoft a délibérément décidé de ne pas faire.
L'inconvénient est la perte de vitesse que j'ai mentionnée précédemment, et aussi parce qu'il n'y a pas d'accès à l'Internet. ListOfPerson
pseudo-classe ou quoi que ce soit d'autre dans les fichiers .class, le code qui le regarde plus tard (avec la réflexion, ou si vous le tirez d'une autre collection où il a été converti en Object
etc.) ne peuvent en aucun cas dire qu'il s'agit d'une liste contenant uniquement Person
et pas n'importe quelle autre liste de tableaux.
Les modèles C++ vous permettent de déclarer quelque chose comme ceci
std::list<Person>* foo = new std::list<Person>();
Cela ressemble aux génériques C# et Java, et cela fera ce que vous pensez qu'il devrait faire, mais en coulisses, différentes choses se passent.
Il a le plus de points communs avec les génériques de C#, en ce sens qu'il construit des génériques spéciaux. pseudo-classes
plutôt que de rejeter les informations de type comme le fait Java, mais c'est une toute autre paire de manches.
C# et Java produisent tous deux des résultats conçus pour les machines virtuelles. Si vous écrivez du code qui a un Person
dans celui-ci, dans les deux cas, des informations sur une classe de Person
sera placé dans le fichier .dll ou .class, et la JVM/CLR en fera quelque chose.
Le C++ produit du code binaire brut x86. Tout est pas un objet, et il n'y a pas de machine virtuelle sous-jacente qui a besoin de connaître une Person
classe. Il n'y a pas d'emballage ou de déballage, et les fonctions n'ont pas besoin d'appartenir à des classes, ou même à quoi que ce soit.
Pour cette raison, le compilateur C++ n'impose aucune restriction sur ce que vous pouvez faire avec les modèles - en fait, tout code que vous pourriez écrire manuellement, vous pouvez demander aux modèles de l'écrire pour vous.
L'exemple le plus évident est l'ajout d'éléments :
En C# et en Java, le système générique doit savoir quelles méthodes sont disponibles pour une classe, et il doit les transmettre à la machine virtuelle. La seule façon de le lui dire est soit de coder en dur la classe réelle, soit d'utiliser des interfaces. Par exemple :
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Ce code ne compilera pas en C# ou en Java, car il ne sait pas que le type T
fournit en fait une méthode appelée Name(). Vous devez lui dire - en C# comme ceci :
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
Ensuite, vous devez vous assurer que les éléments que vous passez à addNames implémentent l'interface IHasName et ainsi de suite. La syntaxe java est différente ( <T extends IHasName>
), mais il souffre des mêmes problèmes.
Le cas "classique" de ce problème est d'essayer d'écrire une fonction qui fait ceci
string addNames<T>( T first, T second ) { return first + second; }
Vous ne pouvez pas réellement écrire ce code parce qu'il n'existe aucun moyen de déclarer une interface avec l'attribut +
dans celui-ci. Vous échouez.
Le C++ ne souffre d'aucun de ces problèmes. Le compilateur ne se soucie pas de transmettre des types à une quelconque VM - si vos deux objets ont une fonction .Name(), il compilera. Si ce n'est pas le cas, il ne le fera pas. C'est simple.
Voilà, c'est fait :-)