S

super-dev — full-stack

@super-dev.app · Full-stack · ★ offen für Aufträge
12,8k Abonnenten·47 Videos·Aktiv seit 2017

Full-Stack-Entwickler .NET / Angular mit DevOps-Erfahrung auf Azure Cloud. Auch zu Hause mit Flutter + Firebase. Ich entwickle Produkte, deploye sie und überwache sie.

Zurück zu den Artikeln
λ
.NET
.NET

Code zur Kompilierzeit generieren: Source Generators

Artikel 5 von 5 — Modernes .NET
Minimal APIs, CQRS, gRPC, Source Generators: sauberes und testbares .NET 8, ohne unnötigen Overhead.

Reflexion hat einen Preis, den man im schlechtesten Moment zahlt: beim Start und im heißen Pfad, im Produktionscode. Source Generators verlagern diese Arbeit ans andere Ende des Zyklus — in die Kompilierung . Der Generator liest Ihren Code, erzeugt weiteren, und der Compiler bindet ihn in die Assembly ein, als hätten Sie ihn von Hand geschrieben.

Inkrementell, nicht die alte API

Die erste Generation von Generatoren ( ISourceGenerator ) lief bei jedem Tastendruck vollständig durch und ruinierte die IDE-Erfahrung. Die richtige API heute ist `IIncrementalGenerator` : Sie baut eine gecachte Pipeline auf, in der nur geänderte Eingaben neu berechnet werden. Die Kompilierung wird in zwei Schritten gefiltert — ein schnelles syntaktisches Prädikat, dann eine aufwändigere semantische Transformation.

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}

Das static auf den Lambdas ist kein Stilmittel: Es stellt sicher, dass keine Capture das Pipeline-Caching unterbricht.

Ein konkretes Beispiel: DI-Registrierung

Das klassische Szenario: Eine Klasse mit einem [RegisterScoped] -Attribut markieren und den Generator den entsprechenden AddScoped -Aufruf erzeugen lassen. Kein Program.cs , das mit jedem Service länger wird, kein Assembly-Scan per Reflexion beim Start.

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}

Program.cs beschränkt sich dann auf ein builder.Services.AddGenerated(); . Der Code ist sichtbar , debuggbar, und der Compiler validiert ihn wie alles andere.

Diagnostics: Warnen, nicht abstürzen

Ein guter Generator beschränkt sich nicht darauf, Code auszugeben: Er leitet den Autor an. Anstatt ungültiges C# zu erzeugen, wenn das Attribut falsch platziert ist, wird ein Diagnostic ausgegeben, das die IDE als nativen Warning oder Fehler genau an der richtigen Stelle in der Quelldatei anzeigt.

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

Dieses Diagnostic wird über context.ReportDiagnostic(...) ausgegeben, sobald der Fall erkannt wird, und der Fehler erscheint im Editor , unterstrichen unter dem fehlerhaften Typ — ohne jemals die Ausführung zu erreichen.

Build-Zeit gegen Reflexion

Der Vorteil geht weit über Performance hinaus. Ein Fehler — ein vergessener Service, ein nicht aufgelöster Typ — taucht zur Kompilierung auf, nicht bei der ersten Anfrage in der Produktion. Der generierte Code liegt vor Ihren Augen (aktivieren Sie EmitCompilerGeneratedFiles , um ihn zu inspizieren), ist trimmbar und AOT/Native -kompatibel — genau dort, wo Reflexion den Linker stolpern lässt. Das ist exakt die Richtung, die das Ökosystem einschlägt: System.Text.Json , Logging und ASP.NET-Optionen migrieren zu Generatoren. Das offizielle Tutorial beschreibt die Pipeline in der doc Roslyn source generators .

Ein Source Generator ist ehrliche Metaprogrammierung: keine Magie zur Laufzeit, nur Code, den Sie von Hand geschrieben hätten — aber den der Compiler für Sie schreibt und dabei gleich überprüft.
super-dev — portfolio.app
// Weiter in .NET
{ }
Einen .NET 8-Monolithen stranglieren, ohne alles zu zerstören
9 min • 3,2k Aufrufe
ƒ()
Minimal APIs + EF Core: eine saubere .NET 8 API
8 min • 3,1k Aufrufe
CQRS und Vertical Slices ohne Overengineering
7 min • 2,3k Aufrufe