La potencia de la Reflexión en C# (Parte 3: Constructores)

La imagen muestra el logo de C#

Volvemos otra semana más hablando de le reflexión en C#, y hoy toca hablar de los constructores. Como recordatorio, las últimas semanas hemos dado unas pinceladas sobre la reflexión y también hemos visto cómo podemos aprovecharla en los ensamblados.

Hoy vamos a seguir buceando un poco en la potencia que nos aporta la reflexión, en este caso para los constructores. Como vimos, es posible crear instancias de clases mediante reflexión utilizando:

var assembly = Assembly.GetAssembly(typeof(Program));

//Creamos el objeto de manera dinámica
var objetoDinamico = assembly.CreateInstance("PostReflexion.ClaseEjemplo",
                                   false,
                                   BindingFlags.ExactBinding,
                                   null,
                                   new object[] { 2 }, /*Argumentos del constructor*/
                                   null,
                                   null);

Pero esto tiene un mayor coste si vamos a utilizar varias veces el mismo constructor. Esto es porque la reflexión, pese a ser un proceso potente, es caro. En la siguiente tabla se puede comparar los resultados entre instanciar el mismo objeto con el código anterior, o buscando su ConstructorInfo, almacenándolo en memoria y llamando solo a este (no te preocupes, ahora vamos a ver que es el ConstructorInfo).

La imagen muestra la comparativa en benchmark de utilizar el constructor de la clase, Assembly.CreateInstance y ConstructorInfo.Invoke, donde se puede ver que Assembly.CreateInstance tarda de media 1362 ns , ConstructorInfo.Invoke tarda de media 218 ns y el constructor de la clase 3 ns

De los datos de la imagen anterior, se puede ver claramente que el proceso de creación de un objeto es hasta 376 veces más lento usando Assembly.CreateInstance, mientras que usando un ConstructorInfo.Invoke solo lo es 60 veces. esto es porque obligamos a ejecutar todo el proceso de búsqueda del constructor de la clase cada vez que queremos crear un objeto (y recordemos la que reflexión es cara, por eso llamar al constructor de la clase siempre es más rápido).

Una vez vistos los datos, está claro que siempre que podamos, lo mejor es no utilizar reflexión, pero si vamos a usarla y encima de manera repetida, es mejor almacenar las partes que necesitemos y no tener que buscarlas cada vez.

Obteniendo los constructores por reflexión

Vale, llegados a este punto, tenemos una cosa clara:

«Un gran poder conlleva una gran responsabilidad»

Tío Ben – Spiderman

Dentro de Assembly, también podemos obtener las declaraciones de tipos («Type«), donde se encuentra toda la información de la definición:

className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

Y es aquí donde empieza «la magia»… Este objeto «Type» nos permite bucear en todos los recovecos que contiene y obtener entre otras cosas un ContructorInfo por cada constructor que se haya definido en el código (en las siguientes entradas iremos viendo el resto de sus posibilidades).

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var constructors = assembly.GetType(className).GetConstructors();

O incluso, nos permite acceder al que coincida con el tipo de argumentos (y el orden) que le pedimos:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Contructor con parametro int          
var constructorConParametros = type.GetConstructor(new[] { typeof(int) }); 
//Constructor genérico
var constructorSinParametros = type.GetConstructor(Type.EmptyTypes); 

Y sin darnos cuenta, ¡ya tenemos nuestro ConstructorInfo listo! Ahora solo nos queda llamar a su método Invoke pasándole como parámetro un array de objetos que cumpla con la firma, es decir, si el constructor espera un «int», le pasaremos un new object[] {int}, si espera un int y un string, new object[] {int,string} ,etc:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Contructor con parametro int
var constructorConParametros = type.GetConstructor(new[] { typeof(int) }); 
//Creamos el objeto de manera dinámica
var objetoDinamicoConParametros = constructorConParametros.Invoke(new object[] { 2 });

Lo que sí es un poco más «raro», es cuando queremos buscar los constructores por reflexión para conseguir el genérico, que hay que utilizar «Type.EmptyTypes» a la hora de buscarlo (y no pasarle nada en el Invoke)

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Constructor genérico           
var constructorSinParametros = type.GetConstructor(Type.EmptyTypes); 

//Creamos el objeto de manera dinámica
var objetoDinamicoSinParametros = constructorSinParametros.Invoke(new object[] { });

¡Vamos a probarlo!

Para probar que funciona, yo he definido una clase con dos constructores así:

public class ClaseEjemplo
{
    private int _valor;
    public ClaseEjemplo(int valor)
    {
        _valor = valor;
    }

    public ClaseEjemplo()
    {
        _valor = 0;
    }

    public int Multiplicar(int por)
    {
        Console.WriteLine($"Llamada a {nameof(Multiplicar)} con parámetro {por}");
        return _valor * por;
    }
}

Y con este código, voy a llamar a sus dos constructores y y a su método Multiplicar (en las próximas entradas veremos en detalle cómo). Según el código, cuando cree la instancia con el constructor genérico, el resultado de llamar a Multiplicar siempre será 0, ya que cualquier número por 0 es 0:

var className = "PostReflexion.ClaseEjemplo";
var assembly = Assembly.GetAssembly(typeof(Program));
var type = assembly.GetType(className);

//Obtenemos los constructores
//Contructor con parametro int
var constructorConParametros = type.GetConstructor(new[] { typeof(int) }); 
//Constructor genérico
var constructorSinParametros = type.GetConstructor(Type.EmptyTypes); 

//Creamos el objeto de manera dinámica
var objetoDinamicoConParametros = constructorConParametros.Invoke(new object[] { 2 });
var objetoDinamicoSinParametros = constructorSinParametros.Invoke(new object[] { });

// Creamos una referencia al método   
var m = assembly.GetType(className).GetMethod("Multiplicar");

//Llamamos al método pasandole el objeto creado dinámicamente y los argumentos dentro de un object[]
var retConstructorParamatrizado = m.Invoke(objetoDinamicoConParametros, new object[] { 3 });
var retConstructorGenerico = m.Invoke(objetoDinamicoSinParametros, new object[] { 3 });
Console.WriteLine($"El retorno de la función con constructor parametrizado es: {retConstructorParamatrizado}");
Console.WriteLine($"El retorno de la función con constructor genérico es: {retConstructorGenerico}");

Tras ejecutar el programa, efectivamente podemos comprobar que el resultado es el esperado:

La imagen muestra el resultado de hacer llamadas a los constructores mediante reflexión

Con esto hemos visto una manera de crear objetos de manera dinámica «eficientemente» gracias a la reflexión en constructores. En las siguientes entradas, seguiremos desgranando como funciona esta maravillosa herramienta que es la reflexión.

He actualizado el repositorio de GitHub con el código para añadir el ejemplo de reflexión en constructores.

3 pensamientos en “La potencia de la Reflexión en C# (Parte 3: Constructores)

  1. Joseba Rodríguez

    Buen artículo.

    Siempre me ha parecido un pelín rebuscada la manera de invocar métodos con la reflexión.

    ¿Qué opinas de usar dynamic?
    dynamic objetoDinamicoConParametros = constructorConParametros.Invoke(new object[] { 2 });
    var retConstructorParamatrizado = objetoDinamicoConParametros.Multiplicar(3);

    Un saludo.

    Responder
    1. JorTurFer Autor

      Buenas Joseba,
      Toda la razón, las invocaciones por reflexión solo hay que utilizarlas cuando no es posible utilizar «dynamic», (te adelantas a mis entradas hablando de ello, eso tocaba la semana que viene XD).
      Los resultados hablan por si solos:
      La imagen muestra que una llamada por reflexión tarda 221 ns mientras que una llamada mediante

      De hecho, la reflexión es muy útil cuando hay que saltarse las modificadores de acceso, por ejemplo invocar métodos/constructores privados o internos.
      Sin ir más lejos, hace 2 días me toco utilizar esa técnica para poder instanciar unos objetos de una librería de terceros que solo tenia un constructor «internal» para poder hacer unas pruebas unitarias.

      El único problema que le veo a «dynamic» respecto a la reflexión, es que mediante reflexión puedes comprobar que las cosas existen y te da más flexibilidad a la hora de buscar, con «dynamic» tienes que saber exactamente a donde accedes o tendrás una excepción en runtime, mientras que por reflexión puedes utilizar más opciones para encontrar lo que buscas (buscar sobre el nombre, la firma, el modificador…).
      Pero sí, divagaciones aparte, tu código es mucho mejor!

      Un abrazo

      Responder
  2. Joseba

    Siento hacer spoilers, jajaja.
    Usar dynamic me recuerda a cuando programábamos sin intellisense (fantástico invento).

    Bueno, y mi código es mejor simplemente porque en tu artículo no tocaba hablar del tema. Estoy seguro que con tus próximas entradas seguiré aprendiendo cosas nuevas.

    Un saludo.

    Responder

Deja un comentario