Metaprogramación en C#: Cuando el código escribe código
Imagina que eres un arquitecto que no solo construye una casa, sino un robot constructor que él mismo levanta paredes, tiende el cableado y pinta las fachadas. En el mundo de C#, este "robot" es la metaprogramación — el arte de escribir programas que crean, analizan o modifican otros programas (o a sí mismos) durante la ejecución.
¿Para qué sirve esto? Imagina que escribes decenas de clases similares para trabajar con JSON, creas sistemas complejos de plugins o te da pereza llamar manualmente cientos de métodos mediante reflexión. La metaprogramación en C# permite automatizar tareas rutinarias, crear frameworks flexibles y acelerar el desarrollo varias veces. Hoy analizaremos los tres pilares de esta técnica: reflexión, árboles de expresión y generación dinámica de código.
Este artículo es para quienes ya escriben con soltura en C#, pero quieren mirar bajo el capó del CLR y aprender la magia que diferencia a un senior de un mid-level. ¡Allá vamos!
Reflexión (Reflection): Rayos X para tu código
La reflexión es la base de la metaprogramación en C#. Permite obtener información sobre tipos, métodos, propiedades y campos durante la ejecución, así como invocarlos dinámicamente. Imagina que miras un objeto como si fuera un libro abierto y puedes leer cualquier página.
Obtener información sobre un tipo
La forma más sencilla es usar la palabra clave typeof o el método GetType():
Type stringType = typeof(string);Console.WriteLine($"Nombre: {stringType.Name}"); // StringConsole.WriteLine($"Espacio de nombres: {stringType.Namespace}"); // SystemConsole.WriteLine($"¿Es una clase? {stringType.IsClass}"); // TrueInvocación dinámica de métodos
Supongamos que tienes una clase Calculator y quieres llamar a su método Add, cuyo nombre solo se conoce en tiempo de ejecución (por ejemplo, desde una configuración):
public class Calculator{ public int Add(int a, int b) => a + b;}
// Usamos reflexiónCalculator calc = new Calculator();Type calcType = typeof(Calculator);MethodInfo? addMethod = calcType.GetMethod("Add");
if (addMethod != null){ int result = (int)addMethod.Invoke(calc, new object[] { 5, 3 }); Console.WriteLine($"Resultado: {result}"); // 8}¿Cuándo es útil la reflexión?
- Serialización / Deserialización — bibliotecas como Newtonsoft.Json usan reflexión activamente.
- Plugins — cargar ensamblados externos y llamar a sus métodos.
- Pruebas — acceso a campos y métodos privados (aunque es mejor no hacerlo).
¡Importante! La reflexión es una operación lenta. Si llamas al mismo método miles de veces, considera almacenar en caché el MethodInfo o usar delegados.
Árboles de expresión (Expression Trees): Código como datos
Los árboles de expresión son una forma de representar código como una estructura de datos (un árbol). Puedes analizar, modificar e incluso compilar este código "sobre la marcha". Es una herramienta poderosa para crear ORM (Entity Framework), validadores y consultas dinámicas.
Creación de un árbol simple
En lugar de escribir la lambda x => x * 2, la construimos manualmente:
using System.Linq.Expressions;
// Creamos el parámetro xParameterExpression param = Expression.Parameter(typeof(int), "x");// Creamos la constante 2ConstantExpression constant = Expression.Constant(2);// Creamos la multiplicación x * 2BinaryExpression body = Expression.Multiply(param, constant);// Lo ensamblamos todo en una lambda: x => x * 2Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(body, param);
// Compilamos y ejecutamosFunc<int, int> compiled