Je sais que Java implémente le polymorphisme paramétrique (génériques) avec effacement. Je comprends ce qu'est l'effacement.
Je sais que C # implémente le polymorphisme paramétrique avec réification. Je sais que ça peut te faire écrire
public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
ou que vous pouvez savoir lors de l' exécution ce que le paramètre de type d' un certain type est paramétré, mais je ne comprends pas ce qu'il est .
- Qu'est-ce qu'un type réifié?
- Qu'est-ce qu'une valeur réifiée?
- Que se passe-t-il lorsqu'un type / une valeur est réifié?
c#
generics
reification
Martijn
la source
la source
if
ication est le processus de conversion d'uneswitch
construction en unif
/else
, alors qu'elle avait déjà été convertie d'unif
/else
à unswitch
...Réponses:
La réification est le processus consistant à prendre une chose abstraite et à créer une chose concrète.
Le terme réification dans les génériques C # fait référence au processus par lequel une définition de type générique et un ou plusieurs arguments de type générique (la chose abstraite) sont combinés pour créer un nouveau type générique (la chose concrète).
Pour l' exprimer autrement, il est le processus de prendre la définition
List<T>
etint
et la production d' un béton deList<int>
type.Pour mieux comprendre, comparez les approches suivantes:
Dans les génériques Java, une définition de type générique est essentiellement transformée en un type générique concret partagé entre toutes les combinaisons d'arguments de type autorisées. Ainsi, plusieurs types (au niveau du code source) sont mappés à un type (niveau binaire) - mais par conséquent, les informations sur les arguments de type d'une instance sont ignorées dans cette instance (effacement de type) .
Dans les génériques C #, la définition de type générique est conservée en mémoire lors de l'exécution. Chaque fois qu'un nouveau type concret est requis, l'environnement d'exécution combine la définition de type générique et les arguments de type et crée le nouveau type (réification). Nous obtenons donc un nouveau type pour chaque combinaison d'arguments de type, au moment de l'exécution .
System.Type
classe (même si la combinaison d'arguments de type générique particulier que vous instanciez n'a pas t apparaissent directement dans votre code source).Dans les modèles C ++, la définition du modèle est conservée en mémoire au moment de la compilation. Chaque fois qu'une nouvelle instanciation d'un type de modèle est requise dans le code source, le compilateur combine la définition de modèle et les arguments de modèle et crée le nouveau type. Nous obtenons donc un type unique pour chaque combinaison des arguments du modèle, au moment de la compilation .
la source
La réification signifie généralement (en dehors de l'informatique) "faire quelque chose de réel".
En programmation, quelque chose est réifié si nous pouvons accéder à des informations à son sujet dans le langage lui-même.
Pour deux exemples complètement non génériques de quelque chose que C # fait et n'a pas réifié, prenons les méthodes et l'accès à la mémoire.
Les langages OO ont généralement des méthodes (et beaucoup d'entre eux n'ont pas de fonctions similaires mais non liées à une classe). En tant que tel, vous pouvez définir une méthode dans un tel langage, l'appeler, peut-être la remplacer, etc. Tous ces langages ne vous permettent pas de traiter la méthode elle-même en tant que données pour un programme. C # (et vraiment, .NET plutôt que C #) vous permet d'utiliser des
MethodInfo
objets représentant les méthodes, donc en C #, les méthodes sont réifiées. Les méthodes en C # sont des "objets de première classe".Tous les langages pratiques ont des moyens d'accéder à la mémoire d'un ordinateur. Dans un langage de bas niveau comme C, nous pouvons traiter directement le mappage entre les adresses numériques utilisées par l'ordinateur, donc les goûts
int* ptr = (int*) 0xA000000; *ptr = 42;
sont raisonnables (tant que nous avons une bonne raison de soupçonner que l'accès à l'adresse mémoire0xA000000
de cette manière a gagné '' t faire sauter quelque chose). En C #, ce n'est pas raisonnable (nous pouvons à peu près le forcer dans .NET, mais avec la gestion de la mémoire .NET, ce n'est pas très susceptible d'être utile). C # n'a pas d'adresses mémoire réifiées.Ainsi, comme refied signifie «rendu réel», un «type réifié» est un type dont nous pouvons «parler» dans la langue en question.
En générique, cela signifie deux choses.
La première est que
List<string>
c'est un type telstring
ou telint
. Nous pouvons comparer ce type, obtenir son nom et s'enquérir:Une conséquence de ceci est que nous pouvons "parler" des types de paramètres d'une méthode générique (ou d'une méthode d'une classe générique) dans la méthode elle-même:
En règle générale, en faire trop est «malodorant», mais cela présente de nombreux cas utiles. Par exemple, regardez:
Cela ne fait pas beaucoup de comparaisons entre le type de
TSource
et divers types pour différents comportements (généralement un signe que vous n'auriez pas dû utiliser du tout des génériques) mais il se partage entre un chemin de code pour les types qui peuvent êtrenull
(devrait retournernull
si aucun élément trouvé, et ne doit pas faire de comparaisons pour trouver le minimum si l'un des éléments comparés estnull
) et le chemin du code pour les types qui ne peuvent pas l'êtrenull
(devrait lancer si aucun élément trouvé, et n'a pas à s'inquiéter de la possibilité d'null
éléments ).Parce qu'il
TSource
est "réel" dans la méthode, cette comparaison peut être faite soit au moment de l'exécution, soit au moment du jitting (généralement au moment du jitting, certainement le cas ci-dessus le ferait au moment du jitting et ne produirait pas de code machine pour le chemin non pris) et nous avons un version «réelle» distincte de la méthode pour chaque cas. (Bien qu'à titre d'optimisation, le code machine est partagé pour différentes méthodes pour différents paramètres de type de référence, car cela peut être sans affecter cela, et nous pouvons donc réduire la quantité de code machine jit).(Il n'est pas courant de parler de réification de types génériques en C #, sauf si vous traitez également avec Java, car en C # nous tenons simplement cette réification pour acquise; tous les types sont réifiés. En Java, les types non génériques sont appelés réifiés car cela est une distinction entre eux et les types génériques).
la source
Min
que ci-dessus est utile? Sinon, il est très difficile de respecter son comportement documenté.Enumerable.Min<TSource>
est différent en ce qu'il ne lance pas pour les types non-référence sur une collection vide, mais renvoie par défaut (TSource), et est documenté uniquement comme "Renvoie la valeur minimale dans une séquence générique." Je dirais que les deux devraient jeter sur une collection vide, ou qu'un élément "zéro" doit être passé comme référence, et le comparateur / la fonction de comparaison doit toujours être transmise)Comme Duffymo l'a déjà noté , la «réification» n'est pas la principale différence.
En Java, les génériques sont essentiellement là pour améliorer le support au moment de la compilation - cela vous permet d'utiliser des collections fortement typées par exemple dans votre code, et de gérer la sécurité des types pour vous. Cependant, cela n'existe qu'au moment de la compilation - le bytecode compilé n'a plus aucune notion de génériques; tous les types génériques sont transformés en types "concrets" (en utilisant
object
si le type générique est illimité), en ajoutant des conversions de type et des vérifications de type si nécessaire.Dans .NET, les génériques font partie intégrante du CLR. Lorsque vous compilez un type générique, il reste générique dans l'IL généré. Ce n'est pas seulement transformé en code non générique comme en Java.
Cela a plusieurs impacts sur le fonctionnement des génériques dans la pratique. Par exemple:
SomeType<?>
vous permettre de transmettre toute implémentation concrète d'un type générique donné. C # ne peut pas faire cela - chaque type générique spécifique ( réifié ) est son propre type.object
. Cela peut avoir un impact sur les performances lors de l'utilisation de types valeur dans de tels génériques. En C #, lorsque vous utilisez un type valeur dans un type générique, il reste un type valeur.Pour donner un exemple, supposons que vous ayez un
List
type générique avec un argument générique. En Java,List<String>
etList<Int>
finira par être exactement du même type au moment de l'exécution - les types génériques n'existent vraiment que pour le code de compilation. Tous les appels à egGetValue
seront respectivement transformés en(String)GetValue
et(Int)GetValue
.En C #,
List<string>
etList<int>
sont de deux types différents. Ils ne sont pas interchangeables et leur sécurité de type est également appliquée lors de l'exécution. Peu importe ce que vous faites,new List<int>().Add("SomeString")
sera jamais le travail - le stockage sous - jacentList<int>
est vraiment une gamme entière, alors qu'en Java, il est nécessairement unobject
tableau. En C #, il n'y a pas de lancers impliqués, pas de boxe, etc.Cela devrait également montrer clairement pourquoi C # ne peut pas faire la même chose que Java avec
SomeType<?>
. En Java, tous les types génériques «dérivés de»SomeType<?>
finissent par être exactement du même type. En C #, tous les différentsSomeType<T>
s spécifiques sont de leur propre type distinct. En supprimant les vérifications à la compilation, il est possible de passer à laSomeType<Int>
place deSomeType<String>
(et vraiment, tout ce que celaSomeType<?>
signifie est "ignorer les vérifications à la compilation pour le type générique donné"). En C #, ce n'est pas possible, même pas pour les types dérivés (c'est-à-dire que vous ne pouvez pas le faireList<object> list = (List<object>)new List<string>();
même sistring
est dérivé deobject
).Les deux implémentations ont leurs avantages et leurs inconvénients. Il y a eu quelques fois où j'aurais aimé pouvoir simplement autoriser
SomeType<?>
comme argument en C # - mais cela n'a tout simplement pas de sens dans la façon dont les génériques C # fonctionnent.la source
List<>
,Dictionary<,>
et ainsi de suite en C #, mais l'écart entre cela et une liste ou un dictionnaire concret donné demande un peu de réflexion à combler. La variance sur les interfaces aide dans certains des cas où nous aurions pu une fois vouloir combler cet écart facilement, mais pas tous.List<>
pour instancier un nouveau type générique spécifique - mais cela signifie toujours créer le type spécifique que vous voulez. Mais vous ne pouvez pas utiliserList<>
comme argument, par exemple. Mais oui, au moins cela vous permet de combler le fossé en utilisant la réflexion.T
peut satisfaire une contrainte de type d'emplacement de stockageU
sont quandT
etU
sont du même type, ouU
est un type qui peut contenir une référence à une instance deT
. Il ne serait pas possible d'avoir un emplacement de stockage de type de manière significative,SomeType<?>
mais il serait en théorie possible d'avoir une contrainte générique de ce type.La réification est un concept de modélisation orienté objet.
Reify est un verbe qui signifie "rendre quelque chose d'abstrait réel" .
Lorsque vous effectuez une programmation orientée objet, il est courant de modéliser des objets du monde réel en tant que composants logiciels (par exemple, fenêtre, bouton, personne, banque, véhicule, etc.)
Il est également courant de réifier des concepts abstraits en composants (par exemple, WindowListener, Broker, etc.)
la source