Quelles sont les différences entre les génériques en C# et Java... et les modèles en C++? [fermé]

j'utilise principalement Java et les génériques sont relativement nouveaux. Je n'arrête pas de lire que Java a pris la mauvaise décision ou que .NET a de meilleures implémentations, etc. etc.

alors, quelles sont les principales différences entre C++, C#, Java dans generics? Avantages/inconvénients de chacun?

204
demandé sur Shog9 2008-08-28 09:08:06

13 réponses

je vais ajouter ma voix au bruit et tenter de clarifier les choses:

C # Generics vous permet 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# ne fait que mettre List<Person> dans le fichier dll .NET, mais à 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 les gens - quelque chose comme ListOfPerson .

L'avantage est que c'est vraiment rapide. Il n'y a pas de moulage ou autre chose, et parce que la dll contient l'information que c'est une liste de Person , autre code qui regarde plus tard en utilisant la réflexion peut dire qu'il contient Person objets (donc vous obtenez intellisense et ainsi de suite).

L'inconvénient de cette est ce vieux code C # 1.0 et 1.1 (avant qu'ils n'aient ajouté des génériques) ne comprend pas ces nouveaux List<something> , donc vous devez convertir manuellement les choses à l'ancienne List pour interopérer avec eux. Ce n'est pas un gros problème, car le code binaire C# 2.0 n'est pas compatible à l'envers. La seule fois où cela arrivera, c'est si vous mettez à jour un vieux code C# 1.0/1.1 en C# 2.0

Java Generics vous permettent de déclarer quelque chose comme ceci.

ArrayList<Person> foo = new ArrayList<Person>();

Sur la surface, il semble le même, et il a en quelque sorte-de l'est. 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 construire un ListOfPerson spécial - il utilise simplement le vieux ArrayList simple qui a toujours été en Java. Lorsque vous obtenez des choses hors du tableau, l'habituel Person p = (Person)foo.get(1); casting-dance doit encore être fait. Le le compilateur vous épargne les presses-clés, mais la vitesse de frappe / casting est encore encourue comme il l'a toujours été.

Quand les gens mentionnent "Type Erasure", c'est de ça qu'ils parlent. Le compilateur insère la lance, et puis "efface" le fait que c'est censé être une liste de Person pas seulement Object

l'avantage de cette approche est que l'ancien code qui ne comprend pas génériques n'a pas à se soucier. C'est encore la avec le même ancien ArrayList comme il l'a toujours. Ceci est plus important dans le monde java parce qu'ils voulaient prendre en charge la compilation du code en utilisant Java 5 avec generics, et le faire tourner sur l'ancienne version 1.4 ou JVM précédente, ce que microsoft a délibérément décidé de ne pas déranger.

L'inconvénient, c'est la vitesse frappé je l'ai mentionné précédemment, et aussi parce qu'il n'y a pas de ListOfPerson pseudo-classe ou quoi que ce soit d'aller dans le .les fichiers de classe, le code qui ressemble à ça plus tard (avec réflexion, ou si vous le retirez d'une autre collection où il a été converti en Object ou ainsi de suite) ne peut en aucune façon dire qu'il est censé être une liste contenant seulement Person et pas seulement une autre liste de tableaux.

les gabarits C++ vous permettent de déclarer quelque chose comme ceci

std::list<Person>* foo = new std::list<Person>();

il ressemble à C# et Java generics, et il fera ce que vous pensez qu'il devrait faire, mais dans les coulisses différentes choses se produisent.

il a le plus en commun avec C# generics en ce qu'il construit spécial pseudo-classes plutôt que de jeter l'information de type loin comme java fait, mais c'est une toute autre bouilloire de poisson.

à la fois C# et Java produce output qui est conçu pour les machines virtuelles. Si vous écrivez un code qui a une classe Person en elle, dans les deux cas, quelques informations sur une classe Person iront dans le .dll ou .fichier de classe, et l' JVM / CLR vont faire des trucs avec ça.

c++ produit du code binaire x86 brut. Tout est et non un objet, et il n'y a pas de machine virtuelle sous-jacente qui a besoin de connaître une classe Person . Il n'y a pas de boxe ou de jeu de dés, et les fonctions n'ont pas à 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 gabarits - essentiellement n'importe quel code que vous pourriez écrire manuellement, vous pouvez obtenir des modèles d'écrire pour vous.

L'exemple le plus évident est l'ajout de choses:

en C# et Java, le système generics doit savoir quelles méthodes sont disponibles pour une classe, et il doit transmettre cela à la machine virtuelle. La seule façon de le dire est soit en codant la classe réelle, soit en utilisant 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, parce qu'il ne sait pas que le type T fournit en fait une méthode appelée Name(). Vous devez le dire-dans C# comme ceci:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

et ensuite vous devez vous assurer que les choses que vous passez pour ajouter des noms 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 "classique" pour ce problème est d'essayer d'écrire une fonction qui fait ce

string addNames<T>( T first, T second ) { return first + second; }

vous ne pouvez pas réellement écrire ce code parce qu'il n'y a aucun moyen de déclarer une interface avec la méthode + . Vous échouer.

C++ ne souffre d'aucun de ces problèmes. Le compilateur ne se soucie pas de passer des types à n'importe quel VM - si vos deux objets ont un .Name () function, it will compile. S'ils ne le font pas, ça ne le fera pas. Simple.

Donc, là vous l'avez :-)

365
répondu Orion Edwards 2013-09-26 03:22:11

C++ utilise rarement la terminologie" génériques". Au lieu de cela, le mot "modèle" est utilisé et est plus précis. Templates décrit une technique pour réaliser un dessin 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 templates C++ n'autorisent pas seulement les arguments de type compilation-time mais aussi les arguments de valeur compilation-time const-value: les templates peuvent être donnés sous forme d'entiers ou même signatures de fonction. Cela signifie que vous pouvez faire des choses assez funky au moment de compiler, par exemple des calculs:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

ce code utilise également l'autre caractéristique distinctive des gabarits C++, à savoir la spécialisation des gabarits. 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é chaque fois que l'argument évalue à 1. Cela me permet de définir une récursion sur les définitions de modèle. Je croyez que cela a d'abord été découvert par Andrei Alexandrescu .

La spécialisation des modèles

est importante pour C++ parce qu'elle tient compte des différences structurelles dans les structures de données. Les gabarits dans leur ensemble est un moyen d'unifier une interface entre les types. Toutefois, bien que cela soit souhaitable, tous les types ne peuvent pas être traités de la même manière dans la mise en œuvre. Les modèles C++ en tiennent compte. C'est à peu près la même différence que OOP fait entre interface et mise en œuvre avec la primauté 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 un type de modèle et les traitent uniformément. En fait, ce n'est pas tout à fait juste: C++ ne fonctionne pas sur les conteneurs mais plutôt sur gammes qui sont définis par deux itérateurs, pointant vers le début et derrière à la fin du conteneur. Ainsi, l'ensemble du contenu est limitée par les itérateurs: commencer <= éléments < fin.

L'utilisation d'itérateurs au lieu de conteneurs est utile car elle permet de fonctionner sur des parties d'un conteneur au lieu de sur l'ensemble.

une autre caractéristique distinctive de C++ est la possibilité de spécialisation partielle pour les gabarits de classe. Ceci est en quelque sorte lié à l'appariement des motifs sur les arguments dans Haskell et autres langages fonctionnels. Par exemple, considérons une classe qui stocke des éléments:

template <typename T>
class Store { … }; // (1)

cela fonctionne pour tout type d'élément. Mais disons que nous pouvons stocker des pointeurs plus efficacement que d'autres types en appliquant un truc spécial. Nous pouvons le faire par partiellement spécialisé pour tous les types de pointeur:

template <typename T>
class Store<T*> { … }; // (2)

maintenant, chaque fois que nous utilisons un modèle de conteneur pour un type, la définition appropriée est utilisé:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
61
répondu Konrad Rudolph 2011-10-19 01:29:33

Anders Hejlsberg lui-même décrit les différences ici " génériques en C#, Java, et C++ ".

35
répondu jfs 2008-08-28 05:14:53

il y a déjà beaucoup de bonnes réponses sur ce que les différences sont, alors laissez-moi donner une perspective légèrement différente et ajouter le pourquoi .

comme déjà expliqué, la principale différence est type erasure , c'est-à-dire le fait que le compilateur Java efface les types génériques et qu'ils ne finissent pas dans le bytecode généré. Cependant, la question est: pourquoi faire cela? Il ne fait pas les sens! Ou est-il?

Quelle est l'alternative? Si vous n'implémentez pas les génériques dans le langage, où do vous les implémentez? Et la réponse est: dans la Machine Virtuelle. Qui rompt avec la compatibilité.

type erasure, 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 compilé sur Java 5 peut encore être déployé sur Java 1.4.

Microsoft, cependant, a décidé de revenir en arrière la compatibilité pour generics. C'est pourquoi les génériques.Net sont" meilleurs " que les génériques Java.

bien sûr, Sun ne sont pas des idiots ou des lâches. La raison pour laquelle ils "se sont dégonflés", était que Java était beaucoup plus vieux et plus répandu que .NET quand ils ont introduit les génériques. (Ils ont été introduits à peu près en même temps dans les deux mondes.) Briser la compatibilité ascendante aurait été une énorme douleur.

Mettre encore une autre façon: en Java, les Génériques sont une partie de la Langue (ce qui signifie qu'elles s'appliquent seulement de Java, pas à d'autres langues), dans .NET ils font partie de la Machine Virtuelle (ce qui signifie qu'ils s'appliquent à tous langues, non seulement C# et Visual Basic.NET).

comparez ceci avec les caractéristiques .NET comme LINQ, les expressions lambda, le type de variable locale inférence, types anonymes et arbres d'expression: ce sont tous langue caractéristiques. C'est pourquoi il y a des différences subtiles entre VB.NET et C#: si ces fonctionnalités faisaient partie de la VM, elles seraient les mêmes dans toutes langues. 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 l'exécuter quand même sur .NET 2.0, à condition que vous n'utilisiez aucune .net 3.5 bibliothèques. Cela ne fonctionnerait pas avec generics et .NET 1.1, mais fonctionnerait avec Java et Java 1.4.

18
répondu Jörg W Mittag 2008-08-29 01:08:19

suivi de mon affectation précédente.

Templates sont l'une des principales raisons pour lesquelles C++ échoue de façon si abyssale à intellisense, indépendamment de L'IDE utilisé. En raison de la spécialisation du modèle, L'IDE ne peut jamais être vraiment sûr si un membre existe ou non. Considérons:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

maintenant, le curseur est à la position indiquée et il est sacrément difficile pour L'IDE de dire à ce point si, et ce que, les membres a a. Pour les autres langages l'analyse serait simple, mais pour C++, Un peu d'évaluation est nécessaire à l'avance.

ça empire. Que se passerait-il si my_int_type était aussi défini dans un modèle de classe? Maintenant son type dépendrait d'un autre argument de type. Et ici, même les compilateurs échouent.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

après un peu de réflexion, un programmeur conclurait que ce code est le même que le précédent: Y<int>::my_type se résout à int , donc b devrait être du même type que a , Non?

faux. Au point où le compilateur essaie de résoudre cette affirmation, il ne sait pas encore Y<int>::my_type ! Par conséquent, il ne sait pas que c'est un type. Cela pourrait être autre chose, par exemple une fonction membre ou un champ. Cela pourrait 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:

X<typename Y<int>::my_type> b;

maintenant, le code se compile. Pour voir comment les ambiguïtés surgissent de cette situation, considérons le code suivant:

Y<int>::my_type(123);

cette instruction de code est parfaitement valide et indique à C++ d'exécuter l'appel de fonction Y<int>::my_type . Cependant ,si my_type n'est pas une fonction mais plutôt un type, cette déclaration sera toujours valide et exécutera un cast spécial (le cast de style de fonction) qui est souvent une invocation du constructeur. Le compilateur ne peut pas dire nous entendons nous devons donc distinguer ici.

14
répondu Konrad Rudolph 2008-09-01 09:21:04

Java et C# ont introduit les génériques après leur première version. Cependant, il y a des différences dans la façon dont les bibliothèques centrales ont changé lorsque les génériques ont été introduits. C#'s les génériques ne sont pas seulement compilateur magie et donc il n'était pas possible de generify existant de la bibliothèque de classes sans casser la compatibilité ascendante.

par exemple, en Java l'existant Collections Framework était complètement genericised . Java n'a pas à la fois une version générique et non-générique des classes de collections. d'une certaine façon, c'est beaucoup plus propre - si vous avez besoin d'utiliser une collection en C# il y a vraiment très peu de raison d'aller avec la version non-générique, mais ces classes d'héritage restent en place, encombrant le paysage.

une autre différence notable est les classes Enum en Java et C#. Java Enum a cette définition un peu tortueuse:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(voir Angelika Langer très clair explication de exactement pourquoi il en est ainsi. Essentiellement, cela signifie que Java peut donner à type un accès sûr à partir d'une chaîne de caractères à sa valeur Enum:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

comparez ceci à la version de C#:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

comme Enum existait déjà en C# avant génériques a été introduit à la langue, la définition ne pourrait pas changer sans briser le code existant. Ainsi, comme les collections, elle demeure dans les bibliothèques centrales de cet état d'héritage.

6
répondu serg10 2008-10-30 00:50:23

11 mois de retard, mais je pense que cette question est prête pour quelques trucs de Joker Java.

ceci est une caractéristique syntaxique de Java. Supposons que vous ayez une méthode:

public <T> void Foo(Collection<T> thing)

et supposons que vous n'avez pas besoin de se référer au type T dans le corps de la méthode. Vous déclarez un nom T et ne l'utilisez qu'une seule fois, alors pourquoi devriez-vous penser à un nom pour cela? Au lieu de cela, vous pouvez écrire:

public void Foo(Collection<?> thing)

le point d'interrogation demande le compilateur pour prétendre que vous avez déclaré un paramètre de type nommé normal qui n'a besoin d'apparaître qu'une seule fois dans cet endroit.

il n'y a rien que vous puissiez faire avec les jokers que vous ne pouvez pas faire avec un paramètre de type nommé (ce qui est la façon dont ces choses sont toujours faites en C++ et C#).

4
répondu Daniel Earwicker 2009-07-10 13:49:57

Wikipedia a de grandes Écritures comparant à la fois Java/C# generics et Java generics/c++ templates. Le article principal sur les génériques semble un peu encombré, mais il a quelques bonnes informations dans elle.

2
répondu travis 2008-08-28 05:14:19

la plainte la plus importante est de type erasure. En cela, les génériques ne sont pas appliqués à l'exécution. voici un lien vers des Sun docs sur le sujet .

génériques sont mis en œuvre par type effacement: les informations de type générique sont présent uniquement au moment de la compilation, après qui il est effacé par le compilateur.

1
répondu Matt Cummings 2008-08-28 05:15:45

les modèles C++ sont en fait beaucoup plus puissants que leurs homologues C# et Java car ils sont évalués au moment de la compilation et de la spécialisation de soutien. Cela permet la méta-programmation de Template et rend le compilateur C++ équivalent à une machine de Turing (i.e. pendant le processus de compilation vous pouvez calculer tout ce qui est calculable avec une machine de Turing).

1
répondu On Freund 2008-08-28 06:32:49

en Java, les génériques sont uniquement des compilateurs, donc vous obtenez:

a = new ArrayList<String>()
a.getClass() => ArrayList

notez que le type de 'a' est une liste de tableaux, pas une liste de chaînes. Donc le type d'une liste de bananes équivaudrait() à une liste de singes.

pour ainsi dire.

1
répondu izb 2008-08-28 07:22:31

ressemble à, Parmi d'autres propositions très intéressantes, il y en a une sur le perfectionnement des génériques et la compatibilité ascendante:

actuellement, les génériques sont mis en œuvre en utilisant l'effacement, ce qui signifie que le l'information de type générique n'est pas disponible à l'exécution, ce qui rend certains type de code difficile à écrire. Les génériques ont été mis en œuvre de cette façon pour soutenir rétrocompatibilité avec l'ancien code non générique. Génériques revérifiés ferait le type générique informations disponibles à l'exécution, qui briserait héritage non-générique code. Toutefois, Neal Gafter a types de fabrication proposés uniquement réifiables si spécifié, afin de ne pas casser rétrocompatibilité.

at article D'Alex Miller sur Java 7 Propositions

1
répondu pek 2010-07-23 07:36:09

NB: Je n'ai pas assez de point pour commenter, alors n'hésitez pas à déplacer ce commentaire à la réponse appropriée.

contrairement à la croyance populaire, que je ne comprends jamais d'où il vient, .net a mis en œuvre de vrais génériques sans rompre la compatibilité ascendante, et ils ont dépensé des efforts explicites pour cela. Vous ne devez pas changer votre code non-générique .net 1.0 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 même jusqu'à 4.0, exactement pour rien d'autre que la raison de compatibilité arrière. Par conséquent, les anciens codes qui utilisaient encore ArrayList non générique fonctionneront toujours et utiliseront la même classe ArrayList qu'auparavant. La compatibilité de code en arrière est toujours maintenue depuis 1.0 jusqu'à maintenant... Donc même dans .net 4.0, vous avez toujours l'option d'utiliser toute classe non-generics de 1.0 BCL si vous choisissez de le faire.

donc je ne pense pas que java doive briser la compatibilité ascendante pour supporter true les génériques.

0
répondu Sheepy 2010-08-03 18:20:58