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à :-)
int addNames<T>( T first, T second ) { return first + second; }
en C #. Le type générique peut être limité à une classe au lieu d'une interface, et il existe un moyen de déclarer une classe avec l'+
opérateur qu'elle contient.C ++ utilise rarement la terminologie «générique». Au lieu de cela, le mot «modèles» est utilisé et est plus précis. Les modèles décrivent une technique pour réaliser une conception générique.
Les modèles C ++ sont très différents de ce que C # et Java implémentent pour deux raisons principales. La première raison est que les modèles C ++ autorisent non seulement les arguments de type à la compilation mais aussi les arguments de valeur const à la compilation: les modèles peuvent être donnés sous forme d'entiers ou même de signatures de fonction. Cela signifie que vous pouvez faire des choses assez funky au moment de la compilation, par exemple des calculs:
Ce code utilise également l'autre caractéristique distinctive des modèles C ++, à savoir la spécialisation des modèles. Le code définit un modèle de classe,
product
qui a un argument de valeur. Il définit également une spécialisation pour ce modèle qui est utilisée chaque fois que l'argument est évalué à 1. Cela me permet de définir une récursivité sur les définitions de modèle. Je crois que cela a été découvert pour la première fois par Andrei Alexandrescu .La spécialisation des modèles est importante pour C ++ car elle permet des différences structurelles dans les structures de données. Les modèles dans leur ensemble sont un moyen d'unifier une interface entre plusieurs types. Cependant, bien que cela soit souhaitable, tous les types ne peuvent pas être traités de manière égale à l'intérieur de l'implémentation. Les modèles C ++ en tiennent compte. C'est à peu près la même différence que la POO fait entre l'interface et l'implémentation avec le remplacement des méthodes virtuelles.
Les modèles C ++ sont essentiels pour son paradigme de programmation algorithmique. Par exemple, presque tous les algorithmes pour les conteneurs sont définis comme des fonctions qui acceptent le type de conteneur comme type de modèle et les traitent uniformément. En fait, ce n'est pas tout à fait vrai: C ++ ne fonctionne pas sur les conteneurs, mais plutôt sur les plages définies par deux itérateurs, pointant vers le début et derrière la fin du conteneur. Ainsi, l'ensemble du contenu est circonscrit par les itérateurs: begin <= elements <end.
L'utilisation d'itérateurs au lieu de conteneurs est utile car elle permet d'opérer sur des parties d'un conteneur plutôt que sur l'ensemble.
Une autre caractéristique distinctive de C ++ est la possibilité de spécialisation partielle pour les modèles de classe. Ceci est quelque peu lié à la correspondance de motifs sur les arguments dans Haskell et dans d'autres langages fonctionnels. Par exemple, considérons une classe qui stocke des éléments:
Cela fonctionne pour tout type d'élément. Mais disons que nous pouvons stocker des pointeurs plus efficacement que les autres types en appliquant une astuce spéciale. Nous pouvons le faire en nous spécialisant partiellement pour tous les types de pointeurs:
Maintenant, chaque fois que nous instancions un modèle de conteneur pour un type, la définition appropriée est utilisée:
la source
Anders Hejlsberg lui-même a décrit les différences ici " Génériques en C #, Java et C ++ ".
la source
Il y a déjà beaucoup de bonnes réponses sur ce que sont les différences, alors permettez - moi de donner une perspective légèrement différente et ajouter le pourquoi .
Comme cela a déjà été expliqué, la principale différence est l' effacement des types , c'est-à-dire le fait que le compilateur Java efface les types génériques et qu'ils ne se retrouvent pas dans le bytecode généré. Cependant, la question est: pourquoi quelqu'un ferait-il cela? Ça n'a pas de sens! Ou alors?
Eh bien, quelle est l'alternative? Si vous n'implémentez pas de génériques dans la langue, où les implémentez-vous? Et la réponse est: dans la machine virtuelle. Ce qui rompt la compatibilité descendante.
L'effacement de type, d'autre part, vous permet de mélanger des clients génériques avec des bibliothèques non génériques. En d'autres termes: le code qui a été compilé sur Java 5 peut toujours être déployé sur Java 1.4.
Microsoft, cependant, a décidé de rompre la compatibilité descendante pour les génériques. C'est pourquoi les génériques .NET sont "meilleurs" que les génériques Java.
Bien sûr, Sun n'est ni idiot ni lâche. La raison pour laquelle ils "se sont dégonflés", c'est que Java était beaucoup plus ancien et plus répandu que .NET lorsqu'ils ont introduit les génériques. (Ils ont été introduits à peu près en même temps dans les deux mondes.) Rompre la compatibilité descendante aurait été une énorme douleur.
En d'autres termes: en Java, les génériques font partie du langage (ce qui signifie qu'ils s'appliquent uniquement à Java, pas aux autres langages), en .NET, ils font partie de la machine virtuelle (ce qui signifie qu'ils s'appliquent à tous les langages, pas juste C # et Visual Basic.NET).
Comparez cela avec les fonctionnalités .NET comme LINQ, les expressions lambda, l'inférence de type de variable locale, les types anonymes et les arborescences d'expression: ce sont toutes des fonctionnalités de langage . C'est pourquoi il existe de subtiles différences entre VB.NET et C #: si ces fonctionnalités faisaient partie de la machine virtuelle, elles seraient les mêmes dans tous les langages. Mais le CLR n'a pas changé: il est toujours le même dans .NET 3.5 SP1 que dans .NET 2.0. Vous pouvez compiler un programme C # qui utilise LINQ avec le compilateur .NET 3.5 et toujours l'exécuter sur .NET 2.0, à condition que vous n'utilisiez aucune bibliothèque .NET 3.5. Ce serait pas travailler avec des génériques et .NET 1.1, mais il ne fonctionne avec Java et Java 1.4.
la source
ArrayList<T>
peut être émis comme un nouveau type nommé en interne avec unClass<T>
champ statique (masqué) . Tant que la nouvelle version de la bibliothèque générique a été déployée avec le code 1.5+ octets, elle pourra s'exécuter sur des JVM 1.4.Suivi de ma publication précédente.
Les modèles sont l'une des principales raisons pour lesquelles C ++ échoue si abyssalement chez intellisense, quel que soit l'IDE utilisé. En raison de la spécialisation des modèles, l'EDI ne peut jamais être vraiment sûr si un membre donné existe ou non. Considérer:
Maintenant, le curseur est à la position indiquée et il est sacrément difficile pour l'IDE de dire à ce point si, et quoi, les membres
a
ont. Pour d'autres langages, l'analyse serait simple mais pour C ++, un peu d'évaluation est nécessaire au préalable.Ça s'empire. Et si
my_int_type
étaient également définis dans un modèle de classe? Maintenant, son type dépendrait d'un autre argument de type. Et ici, même les compilateurs échouent.Après un peu de réflexion, un programmeur conclurait que ce code est le même que ci-dessus: se
Y<int>::my_type
résout àint
,b
devrait donc être du même type quea
, non?Faux. Au point où le compilateur essaie de résoudre cette instruction, il ne le sait
Y<int>::my_type
pas encore! Par conséquent, il ne sait pas qu'il s'agit d'un type. Ce pourrait être autre chose, par exemple une fonction membre ou un champ. Cela peut donner lieu à des ambiguïtés (mais pas dans le cas présent), donc le compilateur échoue. Nous devons lui dire explicitement que nous nous référons à un nom de type:Maintenant, le code se compile. Pour voir comment les ambiguïtés découlent de cette situation, considérez le code suivant:
Cette instruction de code est parfaitement valide et indique à C ++ d'exécuter l'appel de fonction à
Y<int>::my_type
. Cependant, s'ilmy_type
ne s'agit pas d'une fonction mais plutôt d'un type, cette instruction serait toujours valide et effectuerait une conversion spéciale (la conversion de style fonction) qui est souvent une invocation de constructeur. Le compilateur ne peut pas dire ce que nous voulons dire, nous devons donc lever l'ambiguïté ici.la source
Java et C # ont tous deux introduit des génériques après leur première sortie en langage. Cependant, il existe des différences dans la façon dont les bibliothèques de base ont changé lors de l'introduction des génériques. Les génériques de C # ne sont pas seulement la magie du compilateur et il n'était donc pas possible de générer des classes de bibliothèque existantes sans rompre la compatibilité descendante.
Par exemple, en Java, le Framework de collections existant a été complètement générique . Java n'a pas à la fois une version générique et une version non générique héritée des classes de collections. À certains égards, cela est beaucoup plus propre - si vous devez utiliser une collection en C #, il y a vraiment très peu de raisons d'aller avec la version non générique, mais ces classes héritées restent en place, encombrant le paysage.
Une autre différence notable est les classes Enum en Java et C #. L'énumération de Java a cette définition quelque peu tortueuse:
(voir l' explication très claire d'Angelika Langer pour savoir exactement pourquoi il en est ainsi. Essentiellement, cela signifie que Java peut donner un accès sécurisé de type à partir d'une chaîne à sa valeur Enum:
Comparez cela à la version de C #:
Comme Enum existait déjà en C # avant l'introduction des génériques dans le langage, la définition ne pouvait pas changer sans casser le code existant. Ainsi, comme les collections, il reste dans les bibliothèques principales dans cet état hérité.
la source
ArrayList
pourList<T>
et le mettre dans un nouvel espace de noms. Le fait est que s'il y avait une classe apparaissant dans le code source carArrayList<T>
elle deviendrait un nom de classe généré par le compilateur différent dans le code IL, il n'y aurait donc aucun conflit de nom.11 mois de retard, mais je pense que cette question est prête pour certains trucs Java Wildcard.
Il s'agit d'une fonctionnalité syntaxique de Java. Supposons que vous ayez une méthode:
Et supposons que vous n'ayez pas besoin de faire référence au type T dans le corps de la méthode. Vous déclarez un nom T et vous ne l'utilisez qu'une seule fois, alors pourquoi devriez-vous penser à un nom pour lui? Au lieu de cela, vous pouvez écrire:
Le point d'interrogation demande au compilateur de prétendre que vous avez déclaré un paramètre de type nommé normal qui ne doit apparaître qu'une fois à cet endroit.
Il n'y a rien que vous puissiez faire avec des caractères génériques que vous ne pouvez pas faire avec un paramètre de type nommé (c'est ainsi que ces choses sont toujours faites en C ++ et C #).
la source
class Foo<T extends List<?>>
et utiliser,Foo<StringList>
mais en C #, vous devez ajouter ce paramètre de type supplémentaire:class Foo<T, T2> where T : IList<T2>
et utiliser le maladroitFoo<StringList, String>
.Wikipédia a d'excellentes écritures comparant à la fois les génériques Java / C # et les modèles Java génériques / C ++ . L' article principal sur les génériques semble un peu encombré, mais il contient de bonnes informations.
la source
La plus grande plainte est l'effacement du type. En cela, les génériques ne sont pas appliqués lors de l'exécution. Voici un lien vers quelques documents Sun sur le sujet .
la source
Les modèles C ++ sont en réalité beaucoup plus puissants que leurs homologues C # et Java car ils sont évalués au moment de la compilation et prennent en charge la spécialisation. Cela permet la méta-programmation de modèles et rend le compilateur C ++ équivalent à une machine Turing (c'est-à-dire que pendant le processus de compilation, vous pouvez calculer tout ce qui est calculable avec une machine Turing).
la source
En Java, les génériques sont uniquement au niveau du compilateur, vous obtenez donc:
Notez que le type de 'a' est une liste de tableaux, pas une liste de chaînes. Ainsi, le type d'une liste de bananes serait égal à () une liste de singes.
Pour ainsi dire.
la source
On dirait, entre autres propositions très intéressantes, qu'il y en a une sur le raffinement des génériques et la rupture de la rétrocompatibilité:
à l'article d'Alex Miller sur les propositions Java 7
la source
NB: Je n'ai pas assez de points pour commenter, alors n'hésitez pas à déplacer ceci en tant que commentaire pour une réponse appropriée.
Contrairement à la croyance populaire, dont je ne comprends jamais d'où il vient, .net a mis en œuvre de vrais génériques sans rompre la compatibilité descendante, et ils ont consacré des efforts explicites à cela. Vous n'avez pas besoin de changer votre code .net 1.0 non générique en génériques juste pour être utilisé dans .net 2.0. Les listes génériques et non génériques sont toujours disponibles dans .Net Framework 2.0 jusqu'à 4.0, exactement pour rien d'autre que pour des raisons de compatibilité descendante. Par conséquent, les anciens codes qui utilisaient toujours ArrayList non générique continueront de fonctionner et utiliseront la même classe ArrayList qu'avant. La compatibilité du code en amont est toujours maintenue depuis la version 1.0 jusqu'à maintenant ... Donc, même dans .net 4.0, vous avez toujours la possibilité d'utiliser n'importe quelle classe non générique de 1.0 BCL si vous choisissez de le faire.
Je ne pense donc pas que java doive rompre la compatibilité descendante pour prendre en charge les vrais génériques.
la source
ArrayList<Foo>
fichier qu'il souhaite passer à une méthode plus ancienne qui est censée remplir unArrayList
avec des instances deFoo
. Si unArrayList<foo>
n'est pas unArrayList
, comment faire pour que ça marche?