C#
.NET
Backend-Entwicklung
App-Entwicklung

.NET Source Generators

Zweitägiger Workshop zur Roslyn-basierten Compile-Time-Code-Generierung: inkrementelle Source Generators und Analyzers erstellen, Generator-Unit-Tests schreiben, externe Daten zur Compile-Zeit einlesen und C#-Interceptors implementieren — zur Eliminierung von Boilerplate und Verbesserung der Anwendungsperformance.

.NET Source Generators

Dauer: 2 Tage

Zielgruppe

.NET-Entwickler, die Compile-Time-Code-Generierung einsetzen möchten, um Boilerplate zu eliminieren, Muster durchzusetzen und die Anwendungsperformance zu verbessern. Der Workshop richtet sich an Entwickler, die Frameworks, Bibliotheken oder umfangreiche Anwendungen erstellen, bei denen wiederkehrende Code-Muster eine Wartungslast darstellen.

Voraussetzungen

  • Fundierte Kenntnisse in C# und .NET (C# 10+ empfohlen)
  • Grundkenntnisse der Roslyn Compiler API sind hilfreich, aber nicht erforderlich
  • Visual Studio 2022/2026 oder Rider mit installiertem .NET 10 SDK

Lernziele

Nach Abschluss dieses Workshops werden die Teilnehmer in der Lage sein:

  • Zu verstehen, was Source Generators sind, wie sie in die Roslyn-Kompilierungspipeline integriert sind und wann sie gegenüber Alternativen wie Reflection oder T4-Templates vorzuziehen sind
  • Vorhandene Source Generators aus NuGet-Paketen zu konsumieren und in einem Projekt zu konfigurieren
  • Inkrementelle Source Generators mit der IIncrementalGenerator-API von Grund auf zu erstellen
  • Unit Tests für Source Generators mit den Microsoft.CodeAnalysis.Testing-Bibliotheken zu schreiben
  • Externe Daten (XML, JSON) zur Compile-Zeit zu lesen, um die Code-Generierung zu steuern
  • C# Interceptors zu implementieren, um Methodenaufrufe zur Compile-Zeit zu ersetzen, ohne vorhandenen Quellcode zu ändern
  • Performance-Best-Practices anzuwenden: inkrementelle Pipelines, Caching, Diagnose-Reporting und Vermeidung unnötiger Neugenerierung

Kursübersicht

Tag 1 – Vormittag: Source Generators verstehen und verwenden

1. Die Roslyn-Kompilierungspipeline

  • Funktionsweise des C#-Compilers: Syntax Trees, Semantic Models und Symbole
  • Einordnung von Source Generators in die Kompilierungspipeline
  • Source Generators vs. Reflection, T4-Templates und Fody/IL-Weaving: Abwägungen und Anwendungsfälle
  • Anatomie einer generierten Datei: Was wird wann emittiert und wie ist es für den Rest des Projekts sichtbar?

2. Source Generators konsumieren

  • Generator-Pakete aus NuGet einbinden (z.B. System.Text.Json, Microsoft.Extensions.Logging, AutoMapper, Refit)
  • Analyzer- und Generator-Ausgaben in der IDE: Generierte Dateien in Visual Studio und Rider anzeigen
  • <EmitCompilerGeneratedFiles> und <CompilerGeneratedFilesOutputPath>: Generierte Ausgaben zur Inspektion persistieren
  • Generatoren über [assembly: ...]-Attribute und AdditionalFiles konfigurieren
  • Generierten Code debuggen: Breakpoints in generierten Dateien, einzelne Generatoren deaktivieren

3. Den ersten inkrementellen Source Generator schreiben

  • Projekt-Setup: netstandard2.0-Target, NuGet-Referenz auf Microsoft.CodeAnalysis.CSharp, IsRoslynComponent-Property
  • IIncrementalGenerator implementieren und mit [Generator] registrieren
  • Der IncrementalGeneratorInitializationContext: SyntaxProvider, CompilationProvider, AdditionalTextsProvider
  • Syntax-Knoten mit CreateSyntaxProvider und Prädikat/Transformations-Paaren filtern
  • Quellcode mit context.RegisterSourceOutput emittieren
  • Einen einfachen ToString-Helper aus Klassen-Attributen generieren

Lab: Einen Generator erstellen, der Klassen mit dem Attribut [GenerateDto] liest und ein entsprechendes DTO-Record mit gemappten Properties emittiert.

Tag 1 – Nachmittag: Source Generators testen

4. Unit-Test-Strategie

  • Warum Source Generator Tests sich von regulären Unit Tests unterscheiden: kein Runtime, Compiler als Test-Host
  • Die NuGet-Pakete Microsoft.CodeAnalysis.CSharp.Testing und Microsoft.CodeAnalysis.Testing
  • CSharpSourceGeneratorTest<TGenerator, TVerifier>: Anatomie eines Generator-Tests
  • Eingabe-Quellcode bereitstellen, erwartete generierte Ausgabe angeben und Diagnosen prüfen
  • Testen, dass bei nicht passenden Eingaben keine Ausgabe emittiert wird
  • Snapshot-Testing mit Verify.SourceGenerators als alternativer Ansatz

5. Diagnosen und Fehlerfälle testen

  • Diagnosen aus einem Generator melden: DiagnosticDescriptor, Schweregrade, Positionen
  • Testen, dass der Generator bei ungültiger Eingabe die korrekte Warnung oder den richtigen Fehler emittiert
  • Inkrementelles Verhalten testen: Sicherstellen, dass unveränderte Eingaben keine Neugenerierung auslösen
  • Integrationstests: Ein Beispiel-Consumer-Projekt bauen und erfolgreiche Kompilierung prüfen

6. Testgetriebene Generator-Entwicklung

  • Tests vor der Implementierung der Generator-Logik schreiben
  • Generator-Ausgaben durch Aktualisieren erwarteter Snapshots iterativ verbessern
  • CI/CD-Setup: Generator-Tests in einer CI-Pipeline ohne spezielle Werkzeuge ausführen

Lab: Eine vollständige Test-Suite für den Generator vom Vormittag schreiben: Happy Path, fehlendes Attribut, ungültiger Typ und Tests zur Diagnose-Emission.

Tag 2 – Vormittag: Erweiterte Code-Generierung

7. Code aus externen Dateien generieren (XML und JSON)

  • AdditionalFiles in MSBuild: .xml- und .json-Dateien als Generator-Eingaben hinzufügen
  • AdditionalTextsProvider: Dateiinhalte und Pfade zur Compile-Zeit abrufen
  • XML innerhalb des Generators mit System.Xml.Linq parsen
  • JSON mit System.Text.Json (source-generation–sicherer Teilbereich) oder Newtonsoft.Json parsen
  • AdditionalTextsProvider mit CompilationProvider für typbewusste Generierung kombinieren
  • Externe Datei-Reads cachen: WithTrackingName, Collect und Vermeidung von Neuparsen bei unabhängigen Änderungen
  • Stark typisierte Konfigurationsklassen, API-Client-Stubs oder Enum-Definitionen aus Schema-Dateien generieren

8. Attribute und Semantic-Model-Daten lesen

  • Das Semantic Model traversieren: INamedTypeSymbol, IMethodSymbol, IPropertySymbol
  • Attribut-Konstruktor-Argumente und benannte Parameter extrahieren
  • Partielle Klassen und die Anforderung des partial-Schlüsselworts behandeln
  • Extension Methods, Interfaces und verschachtelte Typen aus Semantic-Daten generieren
  • Generics, Nullable Reference Types und Zugriffsmodifikatoren korrekt behandeln

Lab: Einen Generator erstellen, der eine api-schema.xml-AdditionalFile liest und eine stark typisierte C#-Client-Klasse mit je einer Methode pro definiertem Endpunkt emittiert, inklusive XML-Dokumentationskommentare.

Tag 2 – Nachmittag: Interceptors und Performance-Optimierung

9. C# Interceptors

  • Was Interceptors sind: ein Compiler-Feature, das einen bestimmten Aufruf zur Compile-Zeit auf eine andere Methode umleitet
  • Interceptors aktivieren: <Features>interceptors</Features> in der Projektdatei; das [InterceptsLocation]-Attribut
  • [InterceptsLocation] aus einem Source Generator generieren: Dateipfad, Zeile und Spalte aus SyntaxReference ermitteln
  • Anwendungsfälle: Logging-Aufrufe durch Zero-Allocation-Structured-Logging ersetzen, Reflection-basierte Serialisierung durch generierten Code ablösen, Konfigurations-Reads inlinen
  • Einschränkungen und Vorbehalte: ein Interceptor pro Call Site, kein Abfangen von virtuellem Dispatch, Stabilität bei Code-Änderungen
  • Interceptors mit inkrementellen Generatoren für eine vollautomatisierte Optimierungs-Pipeline kombinieren

Lab: Einen Generator schreiben, der alle Aufrufe einer mit [SlowPath] annotierten Methode abfängt und auf eine generierte Fast-Path-Implementierung umleitet, die auf Reflection verzichtet.

10. Source Generators für Build-Performance optimieren

  • Das inkrementelle Generator-Modell: Warum IIncrementalGenerator ISourceGenerator abgelöst hat
  • Die Pipeline für maximales Caching strukturieren: unveränderliche Value Objects, IEquatable<T> auf Pipeline-Eingaben, EquatableArray<T>-Wrapper
  • WithTrackingName für Build-Performance-Diagnosen mit Roslins GeneratorDriverRunResult
  • Aufwändige Arbeit im Transform-Schritt vermeiden: Semantic-Lookups in eine separate Pipeline-Stufe auslagern
  • Generator-Ausführungszeit profilieren: BenchmarkDotNet, Build Binary Logs (-bl) und MSBuild Structured Log Viewer
  • Häufige Fehler: Compilation direkt erfassen, mutablen Zustand verwenden, IEquatable<T> auf Modell-Typen vergessen
  • Parallele und Multi-Output-Generatoren: RegisterImplementationSourceOutput vs. RegisterSourceOutput

11. Diagnosen, Versionierung und Packaging

  • Einen Generator als NuGet-Paket ausliefern: buildTransitive-Assets, Ordner-Layout analyzers/dotnet/cs
  • Generatoren unabhängig von der ergänzten Bibliothek versionieren
  • Spezifische Generator-Warnungen pro Projekt unterdrücken
  • Multi-Targeting: Generator auf netstandard2.0 ausliefern, während die Laufzeitbibliothek auf net10.0 zielt
  • Einen verpackten Generator debuggen: IsRoslynComponent, Source Link und Symbol-Pakete

Lab: Den Generator vom ersten Tag in eine NuGet-fähige Struktur umbauen, Build-Performance-Tracking hinzufügen, inkrementelles Caching in einer Multi-Projekt-Solution verifizieren und die Regenerierungskosten mit und ohne IEquatable<T> auf dem Pipeline-Modell messen.

Praktische Übungen

  • Die Ausgabe der Source Generators System.Text.Json und Microsoft.Extensions.Logging in einem bestehenden Projekt konsumieren und inspizieren.
  • Einen inkrementellen [GenerateDto]-Generator erstellen, der DTO-Records aus annotierten Domain-Klassen emittiert.
  • Eine vollständige Unit-Test-Suite mit Microsoft.CodeAnalysis.CSharp.Testing schreiben: Happy Path, Diagnosen und No-Output-Fälle.
  • Einen Generator erstellen, der eine api-schema.xml-AdditionalFile parst und eine stark typisierte HTTP-Client-Klasse mit XML-Dokumentationskommentaren emittiert.
  • Einen C#-Interceptor-Generator implementieren, der Reflection-basierte Methodenaufrufe durch generierte, Zero-Allocation-Äquivalente ersetzt.
  • Eine inkrementelle Generator-Pipeline profilieren und optimieren: IEquatable<T> zu Modell-Typen hinzufügen, Caching-Verhalten verifizieren und Build-Zeit-Verbesserung messen.
  • Den fertigen Generator als NuGet-Paket mit korrektem buildTransitive-Layout verpacken und aus einem separaten Consumer-Projekt testen.

Ergebnisse

  • In der Lage, zu beurteilen, wann Source Generators das richtige Werkzeug sind, und die Abwägungen gegenüber Reflection, T4 und IL-Weaving zu erläutern
  • Versiert in der Implementierung von IIncrementalGenerator mit korrekt strukturierten, cache-freundlichen Pipelines
  • Kompetent im Testen von Generatoren mit Microsoft.CodeAnalysis.CSharp.Testing inklusive Diagnose- und No-Output-Assertions
  • Fähig, Code-Generierung aus externen XML- und JSON-Schema-Dateien über AdditionalFiles zu steuern
  • Erfahren im Schreiben von C#-Interceptors, um Aufrufstellen zur Compile-Zeit transparent zu ersetzen
  • In der Lage, Generator-Pipelines für Build-Performance mit IEquatable<T>, WithTrackingName und gestuften Transforms zu optimieren
  • Bereit, Generatoren als NuGet-Pakete mit korrektem Multi-Targeting und Asset-Layout auszuliefern

Erweiterte Lernpfade

  • Roslyn-Analyzer und Code Fixes: Generatoren mit Diagnosen kombinieren, die Entwickler zu generierten APIs leiten
  • Scriban- und T4-artige Templates innerhalb von Generatoren für komplexe Multi-File-Ausgaben
  • Integration mit Verify.SourceGenerators für Golden-File-Snapshot-Tests
  • Source Generators in Blazor und MAUI einsetzen: Compile-Time-Routen-Generierung und plattformspezifischer Code
An unhandled error has occurred. Reload 🗙