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
- La reflexión es poderosa pero lenta: puede ser entre 10 y 100 veces más costosa que una llamada directa.
- En escenarios críticos de rendimiento, almacena en caché los objetos
MethodInfoya descubiertos. - La reflexión puede acceder a miembros privados, por lo que las restricciones de seguridad son importantes.
- Las alternativas basadas en
dynamicoExpression<T>pueden ser más eficientes en algunos casos.
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
BindingFlagspara 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
Análisis de código con Roslyn Compiler API en C#
Aprende análisis de código en C# con Roslyn Compiler API, incluyendo árboles de sintaxis y generación de código.
Clases, Objetos, Propiedades y Métodos en C#
Aprende cómo las clases, objetos, propiedades y métodos en C# forman la base de la programación orientada a objetos.
El concepto de los generadores de código en C# (C# 9+)
Aprende generadores de código en C# para generar código en tiempo de compilación y mejorar el rendimiento.
Interfaces y Clases Abstractas en C#
Aprende interfaces y clases abstractas en C#, sus diferencias y cuándo usar cada una para diseñar código limpio y extensible.
Patrones de diseño en C# (Factory, Singleton, Repository, Observer)
Aprende patrones de diseño en C#, como Factory, Singleton y Repository, para desarrollar aplicaciones escalables y mantenibles.
Principios SOLID en C#
Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.