Metaprogramação em C#: Quando o código escreve código
Imagine que você é um arquiteto que não constrói apenas uma casa, mas um robô-construtor que ergue paredes, passa a fiação elétrica e pinta as fachadas sozinho. No mundo do C#, esse "robô" é a metaprogramação — a arte de escrever programas que criam, analisam ou modificam outros programas (ou a si mesmos) durante a execução.
Por que isso é necessário? Imagine que você está escrevendo dezenas de classes semelhantes para trabalhar com JSON, criando sistemas complexos de plugins ou com preguiça de chamar manualmente centenas de métodos por reflexão. A metaprogramação em C# permite automatizar tarefas rotineiras, criar frameworks flexíveis e acelerar o desenvolvimento várias vezes. Hoje, vamos abordar os três pilares dessa técnica: reflexão, árvores de expressão e geração dinâmica de código.
Este artigo é para quem já escreve em C# com confiança, mas quer olhar sob o capô do CLR e aprender a magia que diferencia um sênior de um pleno. Vamos nessa!
Reflexão (Reflection): Raio-X do seu código
A reflexão é a base da metaprogramação em C#. Ela permite obter informações sobre tipos, métodos, propriedades e campos durante a execução, além de invocá-los dinamicamente. Imagine que você olha para um objeto como se fosse um livro aberto e pode ler qualquer página dele.
Obtendo informações sobre o tipo
A maneira mais simples é usar a palavra-chave typeof ou o método GetType():
Type stringType = typeof(string);Console.WriteLine($"Nome: {stringType.Name}"); // StringConsole.WriteLine($"Namespace: {stringType.Namespace}"); // SystemConsole.WriteLine($"É uma classe? {stringType.IsClass}"); // TrueInvocação dinâmica de métodos
Suponha que você tenha uma classe Calculator e queira chamar seu método Add, cujo nome só é conhecido em tempo de execução (por exemplo, a partir de uma configuração):
public class Calculator{ public int Add(int a, int b) => a + b;}
// Usando reflexãoCalculator 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}Quando a reflexão é útil?
- Serialização / Desserialização — bibliotecas como Newtonsoft.Json usam reflexão ativamente.
- Plugins — carregar assemblies externos e chamar seus métodos.
- Testes — acesso a campos e métodos privados (embora seja melhor não fazer isso).
Importante! A reflexão é uma operação lenta. Se você estiver chamando o mesmo método milhares de vezes, considere armazenar em cache o MethodInfo ou usar delegates.
Árvores de Expressão (Expression Trees): Código como dados
Árvores de expressão são uma forma de representar código como uma estrutura de dados (uma árvore). Você pode analisar, modificar e até compilar esse código "em tempo real". É uma ferramenta poderosa para criar ORMs (Entity Framework), validadores e consultas dinâmicas.
Criando uma árvore simples
Em vez de escrever uma lambda x => x * 2, nós a construímos manualmente:
using System.Linq.Expressions;
// Criando o parâmetro xParameterExpression param = Expression.Parameter(typeof(int), "x");// Criando a constante 2ConstantExpression constant = Expression.Constant(2);// Criando a multiplicação x * 2BinaryExpression body = Expression.Multiply(param, constant);// Montando tudo em uma lambda: x => x * 2Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(body, param);
// Compilando e executandoFunc<int, int> compiled