Cargando...

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.

Introducido con C# 9, los Source Generators son una potente característica de Roslyn que permite generar código C# adicional en tiempo de compilación. Este mecanismo crea nuevos archivos de código durante la compilación, no en tiempo de ejecución. De esta forma, el código repetitivo se genera automáticamente sin coste de rendimiento, mejorando la eficiencia y reduciendo el mantenimiento.


¿Qué es un Source Generator?

Un Source Generator es un componente que se añade al compilador. El compilador (Roslyn) analiza el código fuente de tu proyecto y añade automáticamente nuevas clases o métodos donde sea necesario. El código generado se incluye en tiempo de compilación y puede utilizarse como cualquier código C# normal.


// Resumen: un Source Generator es un complemento del compilador.
// El desarrollador escribe una lógica personalizada, el generador produce código.
// Al final de la compilación, el código generado se agrega automáticamente al proyecto.

¿Cuándo usarlo?


Ejemplo simple de Source Generator

A continuación se muestra un ejemplo mínimo de un Source Generator. Este generador crea una nueva clase HelloGenerated durante la compilación.


// HelloGenerator.csproj (debe ser una biblioteca de clases separada)
// Paquete: Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class HelloGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Se ejecuta al inicio del análisis si es necesario (por ejemplo, escucha de sintaxis)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        string code = @"
namespace HelloGen
{
    public static class HelloGenerated
    {
        public static void SayHello() =>
            System.Console.WriteLine(""¡Hola! Esta clase fue generada en tiempo de compilación."");
    }
}";
        context.AddSource("HelloGenerated.g.cs", SourceText.From(code, Encoding.UTF8));
    }
}

// Uso en el proyecto principal
using HelloGen;

class Program
{
    static void Main()
    {
        HelloGenerated.SayHello(); // Llama al código generado en tiempo de compilación
    }
}

Este archivo de código se genera automáticamente durante la compilación. En Visual Studio se puede ver en “Analyzers → HelloGenerator → HelloGenerated.g.cs”.


Estructura: creación de un proyecto de generador

  1. Crea un nuevo proyecto Class Library (.NET Standard).
  2. Agrega las siguientes propiedades al archivo del proyecto:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <AnalyzerLanguage>C#</AnalyzerLanguage>
    <OutputItemType>Analyzer</OutputItemType>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
  </ItemGroup>
</Project>

Con esta configuración, el proyecto se convierte en un “Analyzer/Generator”. Luego puedes referenciar este DLL como “Analyzer” en el proyecto principal.


Generación de código basada en atributos

En escenarios reales, los generadores suelen activarse mediante atributos. Por ejemplo, al agregar el atributo [AutoToString] a una clase, se puede generar automáticamente un método ToString() en tiempo de compilación.


// Código del usuario
[AutoToString]
public partial class Persona
{
    public string Nombre { get; set; }
    public int Edad { get; set; }
}

// Código generado por el generador (en tiempo de compilación)
public partial class Persona
{
    public override string ToString() =>
        $""Persona: Nombre={Nombre}, Edad={Edad}"";
}

Este enfoque elimina el boilerplate (código repetitivo) y facilita el mantenimiento.


Generador incremental (C# 10+)

Introducido con C# 10, el Generador Incremental ofrece soporte de rendimiento y compilación incremental. El código solo se regenera para los archivos modificados.


using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class GeneradorIncrementalSimple : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var clases = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
            .Collect();

        context.RegisterSourceOutput(clases, (spc, listaClases) =>
        {
            foreach (var cls in listaClases)
            {
                string nombre = cls.Identifier.Text;
                string codigo = $@"
namespace AutoGen
{{
    public static class {nombre}Info
    {{
        public static void Escribir() =>
            System.Console.WriteLine(""Clase: {nombre}"");
    }}
}}";
                spc.AddSource($"{nombre}Info.g.cs", SourceText.From(codigo, Encoding.UTF8));
            }
        });
    }
}

Con este modelo, el rendimiento de compilación mejora significativamente en proyectos grandes. Los generadores ahora solo se ejecutan para los archivos afectados.


Source Generator vs Reflection

CaracterísticaSource GeneratorReflection
Momento de ejecuciónTiempo de compilaciónTiempo de ejecución
RendimientoAlto (genera código previamente)Bajo (análisis en tiempo de ejecución)
Casos de usoORM, Serialización, Cliente APIPlugins, Pruebas, Tipos dinámicos
Visibilidad del códigoLos archivos generados son visibles en el proyectoSolo tiene efecto en tiempo de ejecución

Rendimiento y buenas prácticas


Ejemplo: generación automática de DTO

Supongamos que deseas generar clases DTO a partir de modelos que correspondan a tablas de base de datos. El siguiente Source Generator crea una clase {NombreModelo}Dto para cada modelo.


// Clase modelo
[AutoDto]
public class Usuario
{
    public int Id { get; set; }
    public string Nombre { get; set; }
}

// Código generado
public class UsuarioDto
{
    public int Id { get; set; }
    public string Nombre { get; set; }
}

Esto elimina la necesidad de escribir manualmente los DTO y refleja automáticamente los cambios en el modelo.


TL;DR

  • Los Source Generators son herramientas basadas en Roslyn que generan código durante la compilación.
  • No tienen el coste de ejecución de la reflexión; son más eficientes y seguros.
  • Se implementan mediante las interfaces ISourceGenerator o IIncrementalGenerator.
  • El código generado se incluye en la compilación y funciona como código C# normal.
  • Se utilizan ampliamente para serializadores, mapeadores, generadores de DTO y creación de clientes API.

Artículos relacionados