Cargando...

Reflexión y enlace tardío en C#

Aprende Reflection y late binding en C# para inspeccionar tipos en tiempo de ejecución y crear aplicaciones dinámicas.

En C#, la reflexión (Reflection) permite acceder a la información de tipos en tiempo de ejecución (runtime), trabajar con tipos y crear objetos de forma dinámica. La vinculación tardía (Late Binding) consiste en acceder a objetos cuyo tipo es desconocido en tiempo de compilación. Gracias a estos dos mecanismos, es posible desarrollar sistemas flexibles, modulares o basados en plugins.


¿Qué es la Reflexión?

La reflexión es un mecanismo potente que permite inspeccionar y utilizar los tipos (clases, métodos, propiedades, campos, etc.) dentro de un ensamblado (assembly) en tiempo de ejecución. Por ejemplo, puedes ver las clases, métodos, atributos o valores definidos dentro de un archivo de ensamblado.


using System;
using System.Reflection;

class Ejemplo
{
    public int Numero { get; set; }
    public void Escribir() => Console.WriteLine("Método Escribir() llamado.");
}

class Programa
{
    static void Main()
    {
        Type tipo = typeof(Ejemplo);
        Console.WriteLine($"Nombre de la clase: {tipo.Name}");

        // Listar propiedades
        foreach (var prop in tipo.GetProperties())
            Console.WriteLine($"Propiedad: {prop.Name}");

        // Listar métodos
        foreach (var metodo in tipo.GetMethods())
            Console.WriteLine($"Método: {metodo.Name}");
    }
}

En este ejemplo, el objeto Type proporciona acceso a toda la información estructural de la clase. Se pueden usar métodos como GetMethods(), GetProperties() y GetFields() para realizar un análisis detallado.


¿Qué es la Vinculación Tardía (Late Binding)?

La vinculación tardía consiste en crear una instancia de una clase en tiempo de ejecución cuando su tipo es desconocido en tiempo de compilación. Mediante reflexión, se puede cargar la clase, crear el objeto e invocar sus métodos dinámicamente. Se utiliza comúnmente en sistemas de plugins, carga dinámica de DLL externas y administración de tipos dinámicos.


using System;
using System.Reflection;

class Saludador
{
    public void Saludar(string nombre)
    {
        Console.WriteLine($"¡Hola, {nombre}!");
    }
}

class Programa
{
    static void Main()
    {
        // Crear objeto en tiempo de ejecución
        Type tipo = Type.GetType("Saludador");
        object instancia = Activator.CreateInstance(tipo);

        // Llamar a método (Late Binding)
        MethodInfo metodo = tipo.GetMethod("Saludar");
        metodo.Invoke(instancia, new object[] { "Ahmet" });
    }
}

Aquí, la clase Saludador se carga en tiempo de ejecución usando su nombre. Como el tipo no era conocido en tiempo de compilación, esto se denomina vinculación tardía.


Carga Dinámica a través de Assembly

Con los métodos Assembly.LoadFrom() o Assembly.Load(), puedes cargar una DLL externa y descubrir los tipos que contiene. Este enfoque es común en arquitecturas modulares o basadas en plugins.


using System;
using System.Reflection;

class Programa
{
    static void Main()
    {
        // Cargar DLL externa
        Assembly dll = Assembly.LoadFrom("MiBiblioteca.dll");

        // Listar tipos
        foreach (var tipo in dll.GetTypes())
            Console.WriteLine($"Tipo: {tipo.FullName}");

        // Crear instancia y llamar método
        Type tipoObjetivo = dll.GetType("MiBiblioteca.MathHelper");
        object instancia = Activator.CreateInstance(tipoObjetivo);
        MethodInfo metodo = tipoObjetivo.GetMethod("Sumar");

        object resultado = metodo.Invoke(instancia, new object[] { 3, 5 });
        Console.WriteLine($"Resultado de la suma: {resultado}");
    }
}

Con este método, las bibliotecas externas pueden cargarse en tiempo de ejecución y sus tipos y métodos pueden utilizarse dinámicamente.


Acceso Dinámico a Propiedades y Campos

La reflexión no se limita a invocar métodos; también permite acceder y modificar valores de propiedades y campos.


using System;
using System.Reflection;

class Coche
{
    public string Marca { get; set; } = "Ford";
    private int Velocidad = 120;
}

class Programa
{
    static void Main()
    {
        var coche = new Coche();
        Type tipo = coche.GetType();

        // Leer propiedad pública
        var prop = tipo.GetProperty("Marca");
        Console.WriteLine("Marca: " + prop.GetValue(coche));

        // Acceder a campo privado
        var campo = tipo.GetField("Velocidad", BindingFlags.NonPublic | BindingFlags.Instance);
        Console.WriteLine("Velocidad: " + campo.GetValue(coche));

        // Modificar valor
        campo.SetValue(coche, 200);
        Console.WriteLine("Nueva velocidad: " + campo.GetValue(coche));
    }
}

Con BindingFlags puedes controlar el acceso a miembros públicos, privados o estáticos.


Acceso a Información de Atributos

La reflexión también permite acceder a la información de los Attributes definidos en clases o miembros. Esta funcionalidad es ampliamente utilizada en bibliotecas basadas en metadatos (por ejemplo, ORM, serializadores).


using System;

[AttributeUsage(AttributeTargets.Class)]
class InfoAtributo : Attribute
{
    public string Descripcion { get; }
    public InfoAtributo(string descripcion) => Descripcion = descripcion;
}

[InfoAtributo("Esta clase es solo para demostración.")]
class Ejemplo { }

class Programa
{
    static void Main()
    {
        Type tipo = typeof(Ejemplo);
        var attr = (InfoAtributo)Attribute.GetCustomAttribute(tipo, typeof(InfoAtributo));

        Console.WriteLine($"Descripción: {attr.Descripcion}");
    }
}

Este mecanismo permite agregar metadatos a las clases y leerlos en tiempo de ejecución.


Consideraciones de Rendimiento y Seguridad


Ejemplo: Sistema de Plugins

La reflexión se usa comúnmente para construir sistemas modulares o basados en plugins. El siguiente ejemplo busca clases dentro de una DLL que implementan la interfaz IPlugin y las carga dinámicamente.


using System;
using System.Linq;
using System.Reflection;

public interface IPlugin
{
    string Nombre { get; }
    void Ejecutar();
}

class Programa
{
    static void Main()
    {
        Assembly dll = Assembly.LoadFrom("PaquetePlugin.dll");
        var tiposPlugin = dll.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);

        foreach (var tipo in tiposPlugin)
        {
            IPlugin plugin = (IPlugin)Activator.CreateInstance(tipo);
            Console.WriteLine($"Plugin: {plugin.Nombre}");
            plugin.Ejecutar();
        }
    }
}

Con esta estructura, se pueden integrar nuevos plugins en tiempo de ejecución, incluso si eran desconocidos en el momento de la compilación.


TL;DR

  • Reflection: permite obtener información de tipos y acceder dinámicamente en tiempo de ejecución (Type, MethodInfo, PropertyInfo, etc.).
  • Late Binding: cargar y usar clases en tiempo de ejecución que eran desconocidas en compilación.
  • Las DLL externas pueden cargarse dinámicamente con Assembly.LoadFrom().
  • Usa BindingFlags para acceder a miembros privados.
  • Tiene un alto costo de rendimiento — úsalo solo cuando sea necesario.
  • Comúnmente utilizado en plugins, serializadores, ORM y frameworks de pruebas.

Artículos relacionados