S

super-dev — full-stack

@super-dev.app · Full-stack · ★ ouvert aux missions
12,8k abonnés·47 vidéos·Actif depuis 2017

Développeur full-stack .NET / Angular avec une casquette DevOps sur Azure Cloud. Aussi à l'aise sur Flutter + Firebase. Je construis des produits, je les déploie, je les surveille.

Retour aux articles
λ
.NET
.NET

Générer du code à la compilation : les source generators

Article 5 sur 5 — Le .NET moderne
APIs minimales, CQRS, gRPC, source generators : du .NET 8 net et testable, sans usine à gaz.

La réflexion a un coût qu'on paie au pire moment : au démarrage et à chaud, dans le code de production. Les source generators déplacent ce travail à l'autre bout du cycle — à la compilation . Le générateur lit votre code, en produit d'autre, et le compilateur l'inclut dans l'assembly comme si vous l'aviez écrit à la main.

Incremental, pas l'ancienne API

La première vague de générateurs ( ISourceGenerator ) re-tournait tout à chaque frappe et ruinait l'expérience dans l'IDE. La bonne API aujourd'hui est `IIncrementalGenerator` : elle construit un pipeline mis en cache, où seules les entrées modifiées sont recalculées. On filtre la compilation en deux temps — un prédicat syntaxique rapide, puis une transformation sémantique plus coûteuse.

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}

Le static sur les lambdas n'est pas cosmétique : il garantit qu'aucune capture ne casse la mise en cache du pipeline.

Un cas concret : enregistrer la DI

Le scénario classique : marquer une classe d'un attribut [RegisterScoped] , et laisser le générateur produire l'appel AddScoped correspondant. Plus de Program.cs qui s'allonge à chaque service, plus de scan d'assembly par réflexion au démarrage.

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}

Le Program.cs se contente alors d'un builder.Services.AddGenerated(); . Le code est visible , débogable, et le compilateur le valide comme le reste.

Diagnostics : prévenir, pas planter

Un bon générateur ne se contente pas d'émettre du code : il guide l'auteur. Plutôt que de produire du C# invalide quand l'attribut est mal posé, on remonte un diagnostic que l'IDE affiche comme un warning ou une erreur native, exactement au bon endroit du fichier source.

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);

On émet ce diagnostic via context.ReportDiagnostic(...) dès qu'on détecte le cas, et l'erreur apparaît dans l'éditeur , soulignée sous le type fautif — sans jamais atteindre l'exécution.

Build-time contre réflexion

L'intérêt va bien au-delà de la performance. Une erreur — un service oublié, un type non résolu — surgit à la compilation , pas à la première requête en production. Le code généré est sous vos yeux (activez EmitCompilerGeneratedFiles pour l'inspecter), trimmable et compatible AOT/Native — là où la réflexion fait trébucher l'éditeur de liens. C'est exactement la direction prise par l'écosystème : System.Text.Json , le logging et les options ASP.NET migrent vers des générateurs. Le tutoriel officiel détaille le pipeline dans la doc Roslyn source generators .

Un source generator, c'est de la métaprogrammation honnête : pas de magie au runtime, juste du code que vous auriez écrit à la main — mais que le compilateur écrit pour vous, et vérifie au passage.
super-dev — portfolio.app
// Continuer dans .NET
{ }
Étrangler un monolithe .NET 8 sans tout casser
9 min • 3,2k lectures
ƒ()
Minimal APIs + EF Core : une API .NET 8 propre
8 min • 3,1k lectures
CQRS et vertical slices sans usine à gaz
7 min • 2,3k lectures