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?
- Para generar automáticamente código repetitivo difícil de escribir manualmente
- Para sustituir la reflexión por una solución en tiempo de compilación en código crítico de rendimiento
- En sistemas basados en ORM, serializadores, mapeadores, proxies, eventos o atributos
- Cuando se requiere generación de código con tipado seguro, como clientes JSON o API
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
- Crea un nuevo proyecto Class Library (.NET Standard).
- 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ística | Source Generator | Reflection |
|---|---|---|
| Momento de ejecución | Tiempo de compilación | Tiempo de ejecución |
| Rendimiento | Alto (genera código previamente) | Bajo (análisis en tiempo de ejecución) |
| Casos de uso | ORM, Serialización, Cliente API | Plugins, Pruebas, Tipos dinámicos |
| Visibilidad del código | Los archivos generados son visibles en el proyecto | Solo tiene efecto en tiempo de ejecución |
Rendimiento y buenas prácticas
- Los generadores son solo lectura; no pueden modificar archivos existentes, solo agregar código nuevo.
- El código generado aparece con la extensión
.g.csen “Analyzers → Generated Files”. - Evita operaciones pesadas (como acceso a archivos o I/O) dentro del generador.
- Evita generar código innecesario que pueda ralentizar la compilación.
- El texto del código generado debe estar en UTF-8 y agregarse mediante
context.AddSource().
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
ISourceGeneratoroIIncrementalGenerator. - 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
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.
Genéricos en C# (List<T>, Dictionary<TKey,TValue>)
Aprende genéricos en C# (List<T>, Dictionary<TKey,TValue>) para escribir código reutilizable y seguro de tipos con ejemplos.
Optimización del rendimiento con Span<T> y Memory<T> en C#
Aprende optimización de rendimiento en C# con Span<T> y Memory<T> para gestionar memoria de forma eficiente.
Principios SOLID en C#
Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.
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.