Esta es la pregunta desde la perspectiva interna del compilador.
Estoy interesado en los genéricos, no en las plantillas (C ++), así que marqué la pregunta con C #. No Java, porque AFAIK los genéricos en ambos idiomas difieren en las implementaciones.
Cuando miro los idiomas sin genéricos, es bastante sencillo, puede validar la definición de clase, agregarla a la jerarquía y listo.
¿Pero qué hacer con la clase genérica y, lo que es más importante, cómo manejar las referencias a ella? Cómo asegurarse de que los campos estáticos sean singulares por instanciación (es decir, cada vez que se resuelven los parámetros genéricos).
Digamos que veo una llamada:
var x = new Foo<Bar>();
¿Agrego nueva Foo_Bar
clase a la jerarquía?
Actualización: hasta ahora solo encontré 2 publicaciones relevantes, sin embargo, incluso ellas no entran en muchos detalles en sentido "cómo hacerlo usted mismo":
Respuestas:
Cada instanciación genérica tiene su propia copia de la Tabla de métodos (con un nombre confuso), que es donde se almacenan los campos estáticos.
No estoy seguro de que sea útil pensar en la jerarquía de clases como una estructura que realmente existe en tiempo de ejecución, es más una construcción lógica.
Pero si considera las tablas de métodos, cada una con un puntero indirecto a su clase base, para formar esta jerarquía, entonces sí, esto agrega una nueva clase a la jerarquía.
fuente
Foo<string>
y no producirán dos instancias de campo estáticoFoo
.Veo dos preguntas concretas reales allí. Probablemente quiera hacer preguntas relacionadas adicionales (como una pregunta separada con un enlace a este) para obtener una comprensión completa.
¿Cómo se asignan instancias separadas a los campos estáticos por instancia genérica?
Bueno, para los miembros estáticos que no están relacionados con los parámetros de tipo genérico, esto es bastante fácil (use un diccionario asignado de los parámetros genéricos al valor).
Los miembros (estáticos o no) que están relacionados con los parámetros de tipo pueden manejarse mediante borrado de tipo. Simplemente use la restricción más fuerte (a menudo
System.Object
). Debido a que la información de tipo se borra después de las comprobaciones de tipo del compilador, significa que no serán necesarias las comprobaciones de tipo en tiempo de ejecución (aunque aún pueden existir conversiones de interfaz en tiempo de ejecución).¿Cada instancia genérica aparece por separado en la jerarquía de tipos?
No en genéricos .NET. Se tomó la decisión de excluir la herencia de los parámetros de tipo, por lo que resulta que todas las instancias de un genérico ocupan el mismo lugar en la jerarquía de tipos.
Probablemente fue una buena decisión, porque no buscar nombres de una clase base sería increíblemente sorprendente.
fuente
Foo<int>
yFoo<string>
golpearía a los mismos datos conFoo
w / o limitaciones.List<string>
yList<Form>
, dado queList<T>
internamente tiene un miembro de tipoT[]
y no hay restriccionesT
, entonces lo que realmente obtendrá es un código de máquina que manipula unobject[]
. Sin embargo, dado que soloT
se colocan instancias en la matriz, todo lo que sale se puede devolver comoT
sin una verificación de tipo adicional. Por otro lado, si lo hubiera hechoControlCollection<T> where T : Control
, la matriz internaT[]
se convertiríaControl[]
.La forma general en el extremo frontal del compilador es tener dos tipos de instancias de tipo, el tipo genérico (
List<T>
) y un tipo genérico vinculado (List<Foo>
). El tipo genérico define qué funciones existen, qué campos y tiene referencias de tipo genérico donde sea queT
se use. El tipo genérico enlazado contiene una referencia al tipo genérico y un conjunto de argumentos de tipo. Eso tiene suficiente información para que luego pueda generar un tipo concreto, reemplazando las referencias de tipo genérico conFoo
o lo que sean los argumentos de tipo. Este tipo de distinción es importante cuando haces inferencia de tipos y necesitas inferirList<T>
versusList<Foo>
.En lugar de pensar en genéricos como plantillas (que construyen varias implementaciones directamente), puede ser útil pensar en ellos como constructores de tipos de lenguaje funcional (donde los argumentos genéricos son como argumentos en una función que le da un tipo).
En cuanto al back end, no lo sé realmente. Todo mi trabajo con genéricos se ha dirigido a CIL como back-end, por lo que podría compilarlos en los genéricos compatibles allí.
fuente
List<T>
tiene el tipo real (su definición), mientras queList<Foo>
(gracias por la pieza de terminología también) con mi enfoque contienen las declaraciones deList<T>
(por supuesto, ahora obligado aFoo
en lugar deT
).