Sé que Java implementa el polimorfismo paramétrico (genéricos) con borrado. Entiendo lo que es borrar.
Sé que C # implementa polimorfismo paramétrico con la reificación. Sé que eso puede hacerte escribir
public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
o que puede saber en tiempo de ejecución cuál es el parámetro de tipo de algún tipo parametrizado, pero no entiendo cuál es .
- ¿Qué es un tipo reificado?
- ¿Qué es un valor reificado?
- ¿Qué sucede cuando se reifica un tipo / valor?
c#
generics
reification
Martijn
fuente
fuente
if
icación es el proceso de convertir unaswitch
vuelta a un constructoif
/else
, cuando previamente se había convertido de unif
/else
aswitch
...Respuestas:
La reificación es el proceso de tomar una cosa abstracta y crear una cosa concreta.
El término reificación en genéricos de C # se refiere al proceso mediante el cual una definición de tipo genérico y uno o más argumentos de tipo genérico (lo abstracto) se combinan para crear un nuevo tipo genérico (lo concreto).
Para decirlo de otra forma, es el proceso de tomar la definición de
List<T>
yint
y producir un concretoList<int>
tipo.Para comprenderlo mejor, compare los siguientes enfoques:
En los genéricos de Java, una definición de tipo genérico se transforma esencialmente en un tipo genérico concreto compartido entre todas las combinaciones de argumentos de tipo permitidos. Por lo tanto, los tipos múltiples (nivel de código fuente) se asignan a un tipo (nivel binario), pero como resultado, la información sobre los argumentos de tipo de una instancia se descarta en esa instancia (borrado de tipo) .
En genéricos de C #, la definición de tipo genérico se mantiene en la memoria en tiempo de ejecución. Siempre que se requiera un nuevo tipo concreto, el entorno de tiempo de ejecución combina la definición de tipo genérico y los argumentos de tipo y crea el nuevo tipo (reificación). Entonces obtenemos un nuevo tipo para cada combinación de argumentos de tipo, en tiempo de ejecución .
System.Type
clase (incluso si la combinación de argumento de tipo genérico particular que está creando instancias no lo hizo) t aparece en su código fuente directamente).En las plantillas de C ++, la definición de la plantilla se mantiene en la memoria en el momento de la compilación. Siempre que se requiera una nueva instancia de un tipo de plantilla en el código fuente, el compilador combina la definición de la plantilla y los argumentos de la plantilla y crea el nuevo tipo. Entonces obtenemos un tipo único para cada combinación de argumentos de plantilla, en tiempo de compilación .
fuente
Reificación significa generalmente (fuera de la informática) "hacer algo real".
En programación, algo se reifica si podemos acceder a la información en el lenguaje mismo.
Para dos ejemplos completamente no relacionados con genéricos de algo que C # hace y no ha reificado, tomemos métodos y acceso a la memoria.
Los lenguajes OO generalmente tienen métodos (y muchos que no tienen funciones similares, aunque no están vinculadas a una clase). Como tal, puede definir un método en dicho lenguaje, llamarlo, quizás anularlo, etc. No todos estos lenguajes le permiten manejar el método en sí mismo como datos para un programa. C # (y, en realidad, .NET en lugar de C #) le permite utilizar
MethodInfo
objetos que representan los métodos, por lo que en C # los métodos se reifican. Los métodos en C # son "objetos de primera clase".Todos los lenguajes prácticos tienen algunos medios para acceder a la memoria de una computadora. En un lenguaje de bajo nivel como C podemos tratar directamente con el mapeo entre direcciones numéricas utilizadas por la computadora, por lo que los gustos
int* ptr = (int*) 0xA000000; *ptr = 42;
son razonables (siempre y cuando tengamos una buena razón para sospechar que el acceso a la dirección de memoria0xA000000
de esta manera no ganó '' t explotar algo). En C # esto no es razonable (podemos forzarlo en .NET, pero con la gestión de memoria de .NET moviendo las cosas no es muy probable que sea útil). C # no tiene direcciones de memoria reified.Entonces, como refinado significa "hecho real", un "tipo reificado" es un tipo del que podemos "hablar" en el idioma en cuestión.
En genéricos esto significa dos cosas.
Una es que
List<string>
es un tipo tal comostring
oint
son. Podemos comparar ese tipo, obtener su nombre e investigar al respecto:Una consecuencia de esto es que podemos "hablar" sobre los tipos de parámetros de un método genérico (o método de una clase genérica) dentro del propio método:
Como regla general, hacer esto demasiado es "maloliente", pero tiene muchos casos útiles. Por ejemplo, mira:
Esto no hace muchas comparaciones entre el tipo de
TSource
y varios tipos para diferentes comportamientos (generalmente una señal de que no debería haber utilizado genéricos en absoluto), pero se divide entre una ruta de código para los tipos que pueden sernull
(debería devolvernull
si no se encuentra ningún elemento, y no debe hacer comparaciones para encontrar el mínimo si uno de los elementos comparados esnull
) y la ruta del código para los tipos que no se pueden encontrarnull
(debe arrojar si no se encuentra ningún elemento, y no tiene que preocuparse por la posibilidad denull
elementos )Debido a que
TSource
es "real" dentro del método, esta comparación se puede hacer en tiempo de ejecución o tiempo de jitting (generalmente tiempo de jitting, ciertamente el caso anterior lo haría en el momento de jit y no produciría código de máquina para la ruta no tomada) y tenemos un versión "real" separada del método para cada caso. (Aunque como una optimización, el código de máquina se comparte para diferentes métodos para diferentes parámetros de tipo de tipo de referencia, porque puede ser sin afectar esto y, por lo tanto, podemos reducir la cantidad de código de máquina jitted).(No es común hablar sobre la reificación de tipos genéricos en C # a menos que también se trate de Java, porque en C # simplemente damos por sentado esta reificación; todos los tipos están reificados. En Java, los tipos no genéricos se denominan reified porque eso es una distinción entre ellos y los tipos genéricos).
fuente
Min
hace arriba es útil? De lo contrario, es muy difícil cumplir con su comportamiento documentado.Enumerable.Min<TSource>
es diferente en el sentido de que no arroja tipos no referenciales en una colección vacía, pero devuelve el valor predeterminado (TSource), y se documenta solo como "Devuelve el valor mínimo en una secuencia genérica". Yo diría que ambos deberían arrojar una colección vacía, o que un elemento "cero" debería pasarse como una línea de base, y el comparador / la función de comparación siempre debe pasarse)Como ya notó duffymo , la "reificación" no es la diferencia clave.
En Java, los genéricos están básicamente allí para mejorar el soporte en tiempo de compilación: le permite usar colecciones fuertemente tipadas, por ejemplo, en su código, y tener la seguridad de escritura manejada por usted. Sin embargo, esto solo existe en tiempo de compilación: el código de bytes compilado ya no tiene ninguna noción de genéricos; todos los tipos genéricos se transforman en tipos "concretos" (utilizando
object
si el tipo genérico no tiene límites), agregando conversiones de tipos y verificaciones de tipos según sea necesario.En .NET, los genéricos son una característica integral del CLR. Cuando compila un tipo genérico, permanece genérico en el IL generado. No solo se transforma en código no genérico como en Java.
Esto tiene varios impactos sobre cómo funcionan los genéricos en la práctica. Por ejemplo:
SomeType<?>
que permitirle pasar cualquier implementación concreta de un tipo genérico dado. C # no puede hacer esto: cada tipo genérico específico ( reificado ) es su propio tipo.object
. Esto puede tener un impacto en el rendimiento cuando se usan tipos de valor en tales genéricos. En C #, cuando usa un tipo de valor en un tipo genérico, permanece como un tipo de valor.Para dar una muestra, supongamos que tiene un
List
tipo genérico con un argumento genérico. En Java,List<String>
yList<Int>
terminará siendo exactamente el mismo tipo en tiempo de ejecución: los tipos genéricos solo existen realmente para el código de tiempo de compilación. Todas las llamadas a, por ejemploGetValue
, se transformarán en(String)GetValue
y(Int)GetValue
respectivamente.En C #,
List<string>
yList<int>
son dos tipos diferentes. No son intercambiables, y su seguridad de tipo se aplica también en tiempo de ejecución. No importa lo que haces,new List<int>().Add("SomeString")
lo hará nunca más el trabajo - al almacenamiento subyacente enList<int>
es realmente cierta matriz de enteros, mientras que en Java, es necesariamente unaobject
matriz. En C #, no hay moldes involucrados, no hay boxeo, etc.Esto también debería hacer obvio por qué C # no puede hacer lo mismo que Java con
SomeType<?>
. En Java, todos los tipos genéricos "derivados de"SomeType<?>
terminan siendo exactamente el mismo tipo. En C #, todos los diversosSomeType<T>
s específicos son su propio tipo separado. Al eliminar las comprobaciones en tiempo de compilación, es posible pasar enSomeType<Int>
lugar deSomeType<String>
(y realmente, todo lo queSomeType<?>
significa es "ignorar las comprobaciones en tiempo de compilación para el tipo genérico dado"). En C #, no es posible, ni siquiera para los tipos derivados (es decir, no se puede hacerList<object> list = (List<object>)new List<string>();
aunquestring
se derive deobject
).Ambas implementaciones tienen sus pros y sus contras. Hubo algunas ocasiones en las que me hubiera encantado poder permitirlo solo
SomeType<?>
como argumento en C #, pero simplemente no tiene sentido la forma en que funcionan los genéricos de C #.fuente
List<>
,Dictionary<,>
y así sucesivamente en C #, pero la brecha entre eso y una lista o diccionario concreto requiere bastante reflexión para cerrar. La variación en las interfaces ayuda en algunos de los casos en los que alguna vez quisimos cerrar esa brecha fácilmente, pero no en todos.List<>
para crear instancias de un nuevo tipo genérico específico, pero aún así significa crear el tipo específico que desea. Pero no se puede usarList<>
como argumento, por ejemplo. Pero sí, al menos esto le permite cerrar la brecha utilizando la reflexión.T
puede satisfacer una restricción de tipo de ubicación de almacenamientoU
son cuándoT
yU
son del mismo tipo, oU
es un tipo que puede contener una referencia a una instancia deT
. No sería posible tener significativamente una ubicación de almacenamiento de tipo,SomeType<?>
pero en teoría sería posible tener una restricción genérica de ese tipo.La reificación es un concepto de modelado orientado a objetos.
Reify es un verbo que significa "hacer que algo abstracto sea real" .
Cuando realiza una programación orientada a objetos, es común modelar objetos del mundo real como componentes de software (por ejemplo, Ventana, Botón, Persona, Banco, Vehículo, etc.)
También es común reificar conceptos abstractos en componentes también (por ejemplo, WindowListener, Broker, etc.)
fuente