Generación dinámica de código fuente

En .NET existen ya varias formas de incluir código dinámicamente en nuestra aplicación. La más conocida y utilzada es Reflection, que permite ejecutar ensamblados compilados previamente sin necesidad de incluirlos como referencia “fuerte” dentro de nuestro programa. Muchas técnicas como los servicios web, los paquetes NuGet o la inyección de dependencias emplean este sistema.

En .NET 5 dispondremos de una opción más avanzada: la generación dinámica de código fuente desde el programa. Dicho código fuente se analizará y compilará en tiempo de ejecución y nos permitirá generar código muy personalizado y adaptado a situaciones concretas.

Generar código desde el código

El mecanismo para generar código dinámicamente es muy simple. Nuestro ensamblado debe incluir las referencias al namespace CodeAnalysis, ya que el mecanismo es muy similar al que se utiliza para crear analizadores de código. La clase generadora de código debe estar decorada con el atributo [Generator] e implementar el interfaz ISourceGenerator:


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace SourceGeneratorSamples
{
    [Generator]
    public class HelloWorldGenerator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            // Aquí creamos el código fuente
            var sourceBuilder = new StringBuilder(@"
                using System;
		namespace HelloWorldGenerated
		{
		public static class HelloWorld
		   {
			public static void DiHola() 
		           {
			     Console.WriteLine(""¡Hola desde el código generado!"");
				);

            // Finalizamos la creación de código
            sourceBuilder.Append(@"
				      }
				}
				)");

            // Inyectamos el código generado en el compilador
            context.AddSource("helloWorldGenerator", 
                            SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
        }

        public void Initialize(InitializationContext context)
        {
            // No se requiere inicialización
        }
    }
}
  

Ejecutar el código generado es muy sencillo. La llamada al código lo crea, lo compila, lo añade a la memoria y lo ejecuta:

public class MiClase
{
public void MiMetodo()
{
HelloWorldGenerated.HelloWorld.DiHola();
}
}

Como puede verse este mecanismo es mucho más potente que Reflection ya que permite flexibilizar al máximo el código generado. Podéis encontrar más información en el blog oficial de .NET.