La potencia de la Reflexión en C# (Parte 2: Ensamblados)

JorTurFer       2 comentarios en La potencia de la Reflexión en C# (Parte 2: Ensamblados)
La imagen muestra el logo de C# para Reflexión Ensamblados

En la última entrada hablábamos sobre un problema que me había encontrado en el trabajo y como la reflexión me había permitido resolverlo de una manera elegante y con poco código. Al menos, muchísimo menos código del que habría necesitado sin haberla usado.

Pensaba hacer simplemente un par de entradas hablando sobre el tema, pero creo que tiene bastante miga, así que lo mejor va a ser hacer una pequeña serie hablando de todas las posibilidades que nos ofrece esta poderosa herramienta. Así que hoy vamos a empezar con la primera de la lista y la que a su vez las contiene a todas: «Assembly».

Assembly (ensamblado): Los ensamblados son los bloques de creación de las aplicaciones .NET Framework; constituyen la unidad fundamental de implementación, control de versiones, reutilización, ámbitos de activación y permisos de seguridad. Un ensamblado es una colección de tipos y recursos compilados para funcionar en conjunto y formar una unidad lógica de funcionalidad. Los ensamblados proporcionan a Common Language Runtime la información necesaria para conocer las implementaciones de tipos. Para la ejecución, un tipo no existe fuera del contexto de un ensamblado.

Fuente: MSDN

¿Y que es un assembly? Pues dicho con palabras sencillas, es la unidad mínima que el CLR puede ejecutar. Contiene las los módulos, las clases, los tipos… y lo más importante, el manifiesto, que es donde se registran todos los metadatos. (Cuando hablemos de los módulos veremos que la principal diferencia es esta última). Cuando una aplicación arranca, el CLR consulta los metadatos del ensamblado para conocer el punto de entrada a este. Él

static void Main(string[] args)

de toda la vida. Mediante reflexión, podemos obtener esos metadatos de el/los ensamblados cargados en la aplicación, pudiendo saber que versión de fichero estamos ejecutando, obtener una lista detallada de todas las clases y métodos que están disponibles en nuestra aplicación, o incluso cargar nuevos ensamblados en nuestra aplicación y permitir que estén disponibles de manera dinámica.

Ejemplos de uso para metadatos

Por ejemplo, un código como este nos permitiría saber la versión que estamos ejecutando:

using System;
using System.Reflection;

namespace PostReflexion
{
    class Program
    {
        static void Main(string[] args)
        {
            var assembly = Assembly.GetAssembly(typeof(Program));
            Console.WriteLine($"Versión: {assembly.GetName().Version}");
        }
    }
}

U obtener todos los tipos (clases, interfaces, enumeraciones….):

using System;
using System.Reflection;

namespace PostReflexion
{
    class Program
    {
        static void Main(string[] args)
        {
            var assembly = Assembly.GetAssembly(typeof(Program));
            foreach (var type in assembly.DefinedTypes)
            {
                Console.WriteLine(type);
            }
        }
    }
}

-Vale, ¿y eso para qué vale? ¡Tampoco es una grandísima utilidad…!

En si mismo, lo visto anteriormente no es de una gran utilidad, ya que para obtener la versión podríamos utilizar «FileVersionInfo» y saber el contenido de un ensamblado en tiempo de ejecución no nos vale de mucho… ¿O sí?

Una herramienta muy potente que nos da la clase Assembly, es que nos permite crear objetos de manera dinámica, es decir, durante el la ejecución del programa, y sin estar explícitamente escrito en nuestro código.

Imagina que tienes una aplicación que sirve de punto de lanzador de otra serie de aplicaciones contenidas en librerías. A simple vista, tienes 3 opciones:

  • Distribuir la aplicación con todas su dll referenciadas, haciendo que gente que no ha pagado por todas las aplicaciones se tenga que descargar un montón de ficheros que no va a poder usar. (Mayor tamaño de archivos)
  • Generar diferentes soluciones que utilicen unas u otras librerías para generar diferentes paquetes de aplicaciones, pero esto crece de manera exponencial con el número de librerías y tipos de «paquete». (Mayor trabajo de mantenimiento)
  • Listar las librerías disponibles dentro del directorio, obtener de ahí las clases que inician las diferentes aplicaciones y ejecutarlas de manera dinámica.

Nota: Cualquiera de las 3 opciones debería contar con un sistema de autenticación que acredite que realmente tiene derecho a utilizar la aplicación.

Creando instancias de manera dinámica

Para ello, aprovechando la opción que nos da la reflexión de instanciar clases, podríamos hacer algo como esto:

var assembly = Assembly.LoadFile("ruta a la librería");
            
//Creamos el objeto de manera dinámica
var formDinamico = assembly.CreateInstance("Nombre completo de la clase") as Form;

//Si hemos podido crear la instancia, abrimos el formulario
formDinamico?.ShowDialog();

Con este código y gracias a assembly, solo cambiando las rutas de cargar la librería y el nombre de la clase, podemos abrir cualquier formulario que herede de Form (WinForms). Esto mismo se puede aplicar a WPF sin ninguna dificultad.

Además, también es posible crear instancias de clases que tienen un constructor con argumentos, pasándole esos argumentos:

//Código de la clase
//=============================================
public class ClaseEjemplo
{
    private int _valor;
    public ClaseEjemplo(int valor)
    {
        _valor = valor;
    }
    public int Multiplicar(int por)
    {
        Console.WriteLine($"Llamada a {nameof(Multiplicar)} con parámetro {por}");
        return _valor * por;
    }
}

//Código para instanciar y ejecutar sus métodos
//=============================================
var assembly = Assembly.LoadFile("ruta a la librería");
//Creamos el objeto de manera dinámica
var objetoDinamico = assembly.CreateInstance("PostReflexion.ClaseEjemplo"
                               ,false
                               ,BindingFlags.ExactBinding
                               ,null
                               ,new object[]{2} //Contructor
                               ,null  
                               ,null);

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

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

No vamos a entrar a ver como hemos llamado a su método, ya que eso lo veremos cuando lleguemos a esa parte, pero el código es funcional y podemos comprobar como hemos instanciado de manera dinámica la clase y le hemos pasado un valor a su constructor, pudiéndolo comprobar al llamar al método.

Estas son solo alguna de las muchísimas opciones que nos da la reflexión en C# a través de la clase Assembly. Combinándola con las demás que vamos a ver, nos permite ejecutar cualquier código de manera dinámica en tiempo de ejecución, lo cual es una herramienta muy potente y que abre un gran abanico de posibilidades.

He actualizado el repositorio de GitHub para añadir los ejemplos de obtención de metadatos y creación de clases de manera dinámica para poder probar esos conceptos.

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

    1. JorTurFer Autor

      Buenas tardes Joseba,
      ¡gracias por tu comentario! Sí, la reflexión es «cara», no tanto para los assemblies que es algo que se suele hacer al cargar la aplicación, sino para llamadas dinámicas que pueden llamarse muchas veces, aun así, creo que es muy útil y puede salvar más de un apuro.
      Como para todo, también hay trucos para solventarlo usando delegados una vez que encuentras los métodos.
      Te dejo un hilo de stackoverflow donde se habla sobre el tema:
      How costly is .NET reflection?

      Cuando lleguemos a esa parte, tenia pensado poner algunas métricas y algún truco para bajar los tiempos.

Deja un comentario