Saltar al contenido

¿Qué es la reificación?

Hola usuario de nuestra página, encontramos la respuesta a tu interrogante, deslízate y la verás más abajo.

Solución:

La cosificación es el proceso de tomar una cosa abstracta y crear una cosa concreta.

El término cosificación en C # genéricos se refiere al proceso por el cual un definición de tipo genérico y uno o mas argumentos de tipo genérico (lo abstracto) se combinan para crear una nueva tipo genérico (lo concreto).

Para decirlo de otra manera, es el proceso de tomar la definición de List y int y produciendo un concreto List escribe.

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 en todas las combinaciones de argumentos de tipo permitidas. Por lo tanto, varios tipos (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).

    1. Como efecto secundario de esta técnica de implementación, los únicos argumentos de tipo genérico que están permitidos de forma nativa son aquellos tipos que pueden compartir el código binario de su tipo concreto; lo que significa aquellos tipos cuyas ubicaciones de almacenamiento tienen representaciones intercambiables; lo que significa tipos de referencia. El uso de tipos de valor como argumentos de tipo genérico requiere enmarcarlos (colocarlos en un contenedor de tipo de referencia simple).
    2. No se duplica ningún código para implementar genéricos de esta manera.
    3. Se pierde la información de tipo que podría haber estado disponible en tiempo de ejecución (usando la reflexión). Esto, a su vez, significa que la especialización de un tipo genérico (la capacidad de utilizar código fuente para cualquier combinación de argumentos genéricos en particular) es muy restringida.
    4. Este mecanismo no requiere soporte del entorno de ejecución.
    5. Existen algunas soluciones para conservar la información de tipo que puede utilizar un programa Java o un lenguaje basado en JVM.
  • En los genéricos de C #, la definición de tipo genérico se mantiene en la memoria en tiempo de ejecución. Siempre que se requiere un nuevo tipo concreto, el entorno 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.

    1. Esta técnica de implementación permite instanciar cualquier tipo de combinación de argumentos de tipo. El uso de tipos de valor como argumentos de tipo genérico no genera un recuadro, ya que estos tipos tienen su propia implementación. (El boxeo todavía existe en C #, por supuesto, pero sucede en otros escenarios, no en este).
    2. La duplicación de código podría ser un problema, pero en la práctica no lo es, porque las implementaciones suficientemente inteligentes (esto incluye Microsoft .NET y Mono) pueden compartir código para algunas instancias.
    3. Se mantiene la información de tipo, lo que permite la especialización hasta cierto punto, mediante el examen de los argumentos de tipo mediante la reflexión. Sin embargo, el grado de especialización es limitado, como resultado de que se compila una definición de tipo genérico antes de ocurre cualquier cosificación (esto se hace compilando la definición contra las restricciones de los parámetros de tipo; por lo tanto, el compilador debe ser capaz de “comprender” la definición incluso en ausencia de argumentos de tipo específicos).
    4. Esta técnica de implementación depende en gran medida del soporte en tiempo de ejecución y la compilación JIT (por eso a menudo se escucha que los genéricos de C # tienen algunas limitaciones en plataformas como iOS, donde la generación de código dinámico está restringida).
    5. En el contexto de los genéricos de C #, la cosificación la realiza el entorno de ejecución. Sin embargo, si desea comprender de manera más intuitiva la diferencia entre una definición de tipo genérico y un tipo genérico concreto, siempre puede realizar una cosificación por su cuenta, utilizando el System.Type class (incluso si la combinación de argumento de tipo genérico particular que está instanciando no apareció en su código fuente directamente).
  • En las plantillas de C ++, la definición de la plantilla se mantiene en la memoria durante la compilación. Siempre que se requiere una nueva instanciación 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 los argumentos de la plantilla, en tiempo de compilación.

    1. Esta técnica de implementación permite instanciar cualquier tipo de combinación de argumentos de tipo.
    2. Se sabe que esto duplica el código binario, pero una cadena de herramientas suficientemente inteligente aún podría detectar esto y compartir código para algunas instancias.
    3. La definición de la plantilla en sí no se “compila”, solo se compilan realmente sus instancias concretas. Esto impone menos restricciones al compilador y permite un mayor grado de especialización de plantillas.
    4. Dado que las instancias de plantillas se realizan en tiempo de compilación, aquí tampoco se necesita soporte de tiempo de ejecución.
    5. Este proceso se conoce últimamente como monomorfización, especialmente en la comunidad de Rust. La palabra se usa en contraste con polimorfismo paramétrico, que es el nombre del concepto del que provienen los genéricos.

Cosificación significa generalmente (fuera de la informática) “hacer algo real”.

En programación, algo es reificado si podemos acceder a información sobre él en el propio idioma.

Para ver dos ejemplos completamente no 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 que son similares aunque no están vinculados a una clase). Como tal, puede definir un método en dicho lenguaje, llamarlo, quizás anularlo, etc. No todos estos lenguajes le permiten tratar el método en sí como datos para un programa. C # (y realmente, .NET en lugar de C #) le permite hacer uso de MethodInfo objetos que representan los métodos, por lo que en C # los métodos están cosificados. Los métodos en C # son “objetos de primera clase”.

Todos los lenguajes prácticos tienen algún medio 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 de int* ptr = (int*) 0xA000000; *ptr = 42; es razonable (siempre que tengamos una buena razón para sospechar que acceder a la dirección de memoria 0xA000000 de esta manera no explotará algo). En C # esto no es razonable (podemos forzarlo en .NET, pero con la administración de memoria .NET moviendo las cosas, no es muy probable que sea útil). C # no tiene direcciones de memoria reificadas.

Así como refied 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.

Uno es que List es un tipo igual que string o int están. Podemos comparar ese tipo, obtener su nombre y preguntar sobre él:

Console.WriteLine(typeof(List).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List) == (42).GetType()); // False
Console.WriteLine(typeof(List) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List).GenericTypeArguments[0] == typeof(string)); // True

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 método en sí:

public static void DescribeType(T element)

  Console.WriteLine(typeof(T).FullName);

public static void Main()

  DescribeType(42);               // System.Int32
  DescribeType(42L);              // System.Int64
  DescribeType(DateTime.UtcNow);  // System.DateTime

Como regla general, hacer esto demasiado es “maloliente”, pero tiene muchos casos útiles. Por ejemplo, mira:

public static TSource Min(this IEnumerable source)

  if (source == null) throw Error.ArgumentNull("source");
  Comparer comparer = Comparer.Default;
  TSource value = default(TSource);
  if (value == null)
  
    using (IEnumerator e = source.GetEnumerator())
    
      do
      
        if (!e.MoveNext()) return value;
        value = e.Current;
       while (value == null);
      while (e.MoveNext())
      
        TSource x = e.Current;
        if (x != null && comparer.Compare(x, value) < 0) value = x;
      
    
  
  else
  
    using (IEnumerator e = source.GetEnumerator())
    
      if (!e.MoveNext()) throw Error.NoElements();
      value = e.Current;
      while (e.MoveNext())
      
        TSource x = e.Current;
        if (comparer.Compare(x, value) < 0) value = x;
      
    
  
  return value;

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 usado genéricos en absoluto) pero se divide entre una ruta de código para tipos que pueden ser null (debe regresar null si no se encuentra ningún elemento, y no debe hacer comparaciones para encontrar el mínimo si uno de los elementos comparados es null) y la ruta del código para tipos que no se pueden null (debería lanzar si no se encuentra ningún elemento, y no tiene que preocuparse por la posibilidad de null elementos).

Porque TSource es "real" dentro del método, esta comparación se puede hacer en tiempo de ejecución o en tiempo de jit (generalmente en tiempo de jit, ciertamente el caso anterior lo haría en tiempo de jit y no produciría código de máquina para la ruta no tomada) y tenemos un versión "real" 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 alterado).

(No es común hablar de reificación de tipos genéricos en C # a menos que también trate con Java, porque en C # simplemente damos por sentada esta reificación; todos los tipos están cosificados. En Java, los tipos no genéricos se denominan reificado porque esa es una distinción entre ellos y los tipos genéricos).

Como ya señaló Duffymo, la "cosificación" no es la key diferencia.

En Java, los genéricos están básicamente ahí para mejorar el soporte en tiempo de compilación: le permite usar, por ejemplo, colecciones fuertemente tipadas en su código, y controlar la seguridad de tipos 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 es ilimitado), agregando conversiones de tipo y verificaciones de tipo según sea necesario.

En .NET, los genéricos son una característica integral de 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:

  • Java tiene SomeType para permitirle pasar cualquier implementación concreta de un tipo genérico dado. C # no puede hacer esto, cada específico (reificado) tipo genérico es su propio tipo.
  • Los tipos genéricos ilimitados en Java significan que su valor se almacena como un object. Esto puede tener un impacto en el rendimiento cuando se utilizan tipos de valor en dichos 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 y List terminará siendo exactamente del mismo tipo en tiempo de ejecución: los tipos genéricos solo existen realmente para el código en tiempo de compilación. Todas las llamadas a eg GetValue se transformará en (String)GetValue y (Int)GetValue respectivamente.

C ª#, List y List son dos tipos diferentes. No son intercambiables y su seguridad de tipo también se aplica en tiempo de ejecución. No importa lo que hagas, new List().Add("SomeString") voluntad Nunca trabajo: el almacenamiento subyacente en List es De Verdad algún número entero array, mientras que en Java, es necesariamente una object array. En C #, no hay elencos involucrados, no boxeo, etc.

Esto también debería dejar en claro 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 del mismo tipo. En C #, todos los diversos SomeTypes son su propio tipo separado. Al eliminar las comprobaciones en tiempo de compilación, es posible pasar SomeType en lugar de SomeType (y realmente, todo eso SomeType significa "ignorar las comprobaciones en tiempo de compilación para el tipo genérico dado"). En C #, no es posible, ni siquiera para tipos derivados (es decir, no puede hacer List list = (List)new List(); aunque string se deriva de object).

Ambas implementaciones tienen sus pros y sus contras. Ha habido algunas ocasiones en las que me hubiera encantado poder permitir SomeType como argumento en C #, pero simplemente no tiene sentido la forma en que funcionan los genéricos de C #.

Tienes la opción de añadir valor a nuestro contenido informacional añadiendo tu experiencia en las críticas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Respuestas a preguntas comunes sobre programacion y tecnología