S

super-dev — full-stack

@super-dev.app · Full-stack · ★ disponible para proyectos
12,8k suscriptores·47 vídeos·Activo desde 2017

Desarrollador full-stack .NET / Angular con perfil DevOps en Azure Cloud. También cómodo con Flutter + Firebase. Construyo productos, los despliego, los monitorizo.

Volver a los artículos
λ
.NET
.NET

Generar código en compilación: los source generators

Artículo 5 de 5 — El .NET moderno
APIs mínimas, CQRS, gRPC, source generators: .NET 8 limpio y testeable, sin complicaciones.

La reflexión tiene un coste que se paga en el peor momento: en el arranque y en la ruta caliente, en el código de producción. Los source generators trasladan ese trabajo al otro extremo del ciclo — a la compilación . El generador lee tu código, produce más código, y el compilador lo incluye en el assembly como si lo hubieras escrito a mano.

Incremental, no la API antigua

La primera oleada de generadores ( ISourceGenerator ) recalculaba todo en cada pulsación y arruinaba la experiencia en el IDE. La API correcta hoy es `IIncrementalGenerator` : construye un pipeline en caché, donde solo se recalculan las entradas modificadas. Filtramos la compilación en dos tiempos — un predicado sintáctico rápido, luego una transformación semántica más costosa.

C#
1[Generator]
2public sealed class ServiceRegistrationGenerator : IIncrementalGenerator
3{
4 public void Initialize(IncrementalGeneratorInitializationContext context)
5 {
6 var services = context.SyntaxProvider.ForAttributeWithMetadataName(
7 "MyApp.RegisterScopedAttribute",
8 predicate: static (node, _) => node is ClassDeclarationSyntax,
9 transform: static (ctx, _) => ctx.TargetSymbol.ToDisplayString());
10
11 context.RegisterSourceOutput(services.Collect(), Emit);
12 }
13}

El static en las lambdas no es cosmético: garantiza que ninguna captura rompa el caché del pipeline.

Un caso concreto: registrar la DI

El escenario clásico: marcar una clase con el atributo [RegisterScoped] y dejar que el generador produzca la llamada AddScoped correspondiente. Sin más Program.cs que se alarga con cada servicio, sin más escaneo de assembly por reflexión en el arranque.

C#
1private static void Emit(SourceProductionContext context, ImmutableArray<string> types)
2{
3 var registrations = string.Join(
4 "\n ",
5 types.Select(type => $"services.AddScoped<{type}>();"));
6
7 context.AddSource("ServiceRegistrations.g.cs", $$"""
8 namespace MyApp;
9
10 public static class GeneratedServices
11 {
12 public static IServiceCollection AddGenerated(this IServiceCollection services)
13 {
14 {{registrations}}
15 return services;
16 }
17 }
18 """);
19}

El Program.cs se limita entonces a un builder.Services.AddGenerated(); . El código es visible , depurable, y el compilador lo valida como el resto.

Diagnósticos: prevenir, no fallar

Un buen generador no se limita a emitir código: guía al autor. En lugar de producir C# inválido cuando el atributo está mal colocado, se emite un diagnóstico que el IDE muestra como un warning o un error nativo, exactamente en el lugar correcto del archivo fuente.

C#
1private static readonly DiagnosticDescriptor MustBeConcrete = new(
2 id: "MYAPP001",
3 title: "Type non instanciable",
4 messageFormat: "'{0}' est abstrait ou statique et ne peut pas être enregistré en DI",
5 category: "DependencyInjection",
6 DiagnosticSeverity.Error,
7 isEnabledByDefault: true);

Se emite este diagnóstico mediante context.ReportDiagnostic(...) en cuanto se detecta el caso, y el error aparece en el editor , subrayado bajo el tipo defectuoso — sin llegar nunca a la ejecución.

Build-time frente a reflexión

El beneficio va mucho más allá del rendimiento. Un error — un servicio olvidado, un tipo no resuelto — surge en la compilación , no en la primera petición en producción. El código generado está a la vista (activa EmitCompilerGeneratedFiles para inspeccionarlo), trimmable y compatible con AOT/Native — donde la reflexión hace tropezar al enlazador. Es exactamente la dirección que toma el ecosistema: System.Text.Json , el logging y las opciones de ASP.NET migran hacia generadores. El tutorial oficial detalla el pipeline en la doc Roslyn source generators .

Un source generator es metaprogramación honesta : sin magia en tiempo de ejecución, solo código que habrías escrito a mano — pero que el compilador escribe por ti, y valida de paso.
super-dev — portfolio.app
// Continuar en .NET
{ }
Estrangular un monolito .NET 8 sin romper nada
9 min • 3,2k lecturas
ƒ()
Minimal APIs + EF Core: una API .NET 8 limpia
8 min • 3,1k lecturas
CQRS y vertical slices sin sobreingeniería
7 min • 2,3k lecturas