IIncrementalGenerator vs ISourceGenerator: Ein Vergleich der Roslyn Source Generator Schnittstellen

IIncrementalGenerator vs ISourceGenerator: Ein Vergleich der Roslyn Source Generator Schnittstellen

15. Sept. 2024 | Gregor Koletzki | 4 Min. Lesezeit

Die Roslyn Source Generators revolutionieren seit ihrer Einführung die Art und Weise, wie in C# zur Kompilierzeit Code generiert wird. Während sie Entwicklern die Möglichkeit geben, Boilerplate-Code zu reduzieren und komplexe Aufgaben zu automatisieren, gibt es zwei verschiedene Schnittstellen für die Implementierung von Source Generators: ISourceGenerator und IIncrementalGenerator. Beide haben ihre eigenen Anwendungsfälle und Vorteile, wobei IIncrementalGenerator eine Weiterentwicklung mit einem klaren Fokus auf Performance und Effizienz darstellt.

In diesem Artikel werden wir die Unterschiede, Vor- und Nachteile der beiden Schnittstellen untersuchen und herausarbeiten, wann welche verwendet werden sollte.

Überblick: Was sind Source Generators?

Source Generators sind Compiler-Erweiterungen, die es Entwicklern ermöglichen, während der Kompilierung zusätzlichen C#-Code zu erzeugen. Dieser Prozess läuft zur Kompilierzeit ab, sodass Entwickler keine manuellen Codeteile mehr pflegen müssen. Typische Anwendungsfälle sind:

  • Automatisierung von sich wiederholenden Codemustern (z. B. INotifyPropertyChanged).
  • Generierung von Serialisierungs- oder Deserialisierungscode.
  • Code, der auf Basis von Attributen oder Metadaten erstellt wird (z. B. Datenbankabfragen oder Mappings).

ISourceGenerator: Der Ausgangspunkt der Code-Generierung

Die erste Generation von Roslyn Source Generators wird durch die Schnittstelle ISourceGenerator implementiert. Sie ist relativ einfach aufgebaut und arbeitet mit zwei Hauptmethoden:

  • Initialize: Diese Methode wird einmal beim Start des Generators aufgerufen, um eventuelle Konfigurationen durchzuführen, wie das Registrieren von Syntax- oder Compilation-Empfängern.

  • Execute: Hier wird der Hauptteil der Arbeit verrichtet. Der Generator erhält die aktuelle Compilation sowie den Syntaxbaum und kann darauf basierend zusätzlichen Code generieren.

Beispiel einer ISourceGenerator-Implementierung:

[Generator]
public class SimpleGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Hier kann ein Syntax-Receiver oder andere Initialisierungsschritte hinzugefügt werden.
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // Einfaches Beispiel: Hinzufügen einer Klasse zur Compilation.
        var source = @"
        namespace GeneratedNamespace {
            public class GeneratedClass {
                public string SayHello() => ""Hello, World!"";
            }
        }";

        context.AddSource("GeneratedClass.g.cs", source);
    }
}

Der große Vorteil von ISourceGenerator ist seine Einfachheit. Der Entwickler hat direkte Kontrolle über den gesamten Prozess und kann den vollständigen Syntaxbaum und die Compilation verwenden, um Code zu generieren.

Nachteile von ISourceGenerator:

  • Leistungsprobleme: Da der gesamte Syntaxbaum jedes Mal verarbeitet wird, wenn sich auch nur eine kleine Änderung im Code ergibt, kann dies bei größeren Projekten zu Performanceproblemen führen.
  • Keine inkrementelle Verarbeitung: Jeder Schritt muss bei jeder Kompilierung erneut ausgeführt werden, selbst wenn sich der Großteil des Codes nicht geändert hat.
  • Komplexer Code für große Projekte: Da der Generator keinen Mechanismus zur feingranularen Verarbeitung bietet, kann es bei umfangreichen Projekten schwierig sein, den Generator effizient zu gestalten.

IIncrementalGenerator: Eine effizientere Alternative

Mit .NET 6 wurde die neue Schnittstelle IIncrementalGenerator eingeführt, die eine inkrementelle, pipeline-basierte Verarbeitung ermöglicht. Sie wurde entwickelt, um die Performanceprobleme traditioneller Source Generators zu lösen, insbesondere in großen Codebasen.

Funktionsweise von IIncrementalGenerator

IIncrementalGenerator führt den Code-Generierungsprozess in mehreren Schritten aus, wobei nur die Teile des Codes neu verarbeitet werden, die sich tatsächlich geändert haben. Dies geschieht durch eine Pipeline, bei der die Eingabedaten (z. B. der Syntaxbaum oder bestimmte Attribute) stufenweise transformiert werden. Jeder Schritt wird nur dann erneut ausgeführt, wenn sich die Eingabe seit der letzten Kompilierung geändert hat.

Die Methode Initialize funktioniert in IIncrementalGenerator anders als bei ISourceGenerator. Anstatt den gesamten Syntaxbaum zu verarbeiten, definiert der Entwickler eine Pipeline, die schrittweise Daten sammelt und transformiert.

Beispiel einer IIncrementalGenerator-Implementierung:

[Generator]
public class IncrementalHelloWorldGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Definiere eine Pipeline, die den generierten Code nur bei Änderungen neu erzeugt
        context.RegisterSourceOutput(context.CompilationProvider, (ctx, compilation) =>
        {
            var source = @"
            namespace GeneratedNamespace {
                public class GeneratedClass {
                    public string SayHello() => ""Hello, Incremental World!"";
                }
            }";
            ctx.AddSource("GeneratedClass.g.cs", source);
        });
    }
}

Vorteile von IIncrementalGenerator:

  • Leistungsoptimierung: Da nur geänderte Teile des Codes neu verarbeitet werden, kann dies die Kompilierungszeiten drastisch verkürzen, insbesondere bei großen Projekten.
  • Skalierbarkeit: IIncrementalGenerator ist speziell für große Codebasen entwickelt worden. Durch die inkrementelle Pipeline-Verarbeitung bleibt der Generator performant, auch wenn Tausende von Dateien und Klassen vorhanden sind.
  • Deterministische Schritte: Die einzelnen Schritte der Pipeline sind isoliert, was bedeutet, dass der Code-Generierungsprozess deterministisch und leichter nachvollziehbar ist.

Nachteile von IIncrementalGenerator:

  • Komplexität: Die Einrichtung einer inkrementellen Pipeline ist komplizierter als die Implementierung eines einfachen ISourceGenerators. Dies kann die Lernkurve erhöhen, insbesondere für Entwickler, die neu in der Welt der Source Generators sind.
  • Nicht immer notwendig: In kleinen Projekten oder bei Generatoren, die nur wenige Datenpunkte verarbeiten, bietet IIncrementalGenerator möglicherweise keinen signifikanten Vorteil. Die zusätzliche Komplexität könnte in solchen Fällen unnötig sein.

Vergleich der beiden Ansätze

Merkmal ISourceGenerator IIncrementalGenerator
Performance Verarbeitet den gesamten Syntaxbaum bei jeder Kompilierung Verarbeitet nur geänderte Teile des Codes
Komplexität Einfach und leicht zu implementieren Komplexere Pipeline-basierte Architektur
Skalierbarkeit Kann bei großen Projekten langsam werden Ideal für große Codebasen, da inkrementell
Nutzungsszenarien Geeignet für kleinere, einfache Generatoren Optimal für große Projekte mit vielen Abhängigkeiten
Verarbeitung Keine inkrementelle Verarbeitung, alles wird jedes Mal neu verarbeitet Inkrementelle Verarbeitung, jeder Schritt wird nur bei Bedarf erneut ausgeführt

Wann sollte man ISourceGenerator oder IIncrementalGenerator verwenden?

  • Verwendung von ISourceGenerator: Wenn Sie in einem kleinen oder mittleren Projekt arbeiten und die Komplexität Ihrer Generatoren überschaubar ist, reicht ISourceGenerator in den meisten Fällen aus. Für einfache Aufgaben wie das Hinzufügen von Boilerplate-Code oder die Generierung kleiner zusätzlicher Klassen ist diese Schnittstelle ausreichend.

  • Verwendung von IIncrementalGenerator: Wenn Ihr Projekt eine große Codebasis hat und die Kompilierungszeiten lang werden, sollten Sie IIncrementalGenerator in Betracht ziehen. Besonders wenn Ihr Generator viele Datenpunkte verarbeitet oder mit Attributen arbeitet, die sich nur selten ändern, kann die inkrementelle Verarbeitung erhebliche Performancegewinne bringen.

Fazit

Die Entscheidung zwischen ISourceGenerator und IIncrementalGenerator hängt stark vom Projektumfang und den spezifischen Anforderungen ab. Während ISourceGenerator einfacher zu verwenden ist und für kleinere Projekte gut funktioniert, bietet IIncrementalGenerator deutliche Performance- und Skalierungsvorteile für größere Codebasen. In Zukunft werden wir vermutlich eine zunehmende Verlagerung hin zu IIncrementalGenerator sehen, da moderne Projekte immer größere Codebasen umfassen und die Effizienz der Kompilierung immer wichtiger wird.

Weiterführende Artikel

SmartEnums in C#15. Sept. 2024

Kategorien

Kontaktieren Sie uns:

Harksheider Weg 60H,
25451 Quickborn
+49 1520 40 73 253
info@gk-itsolutions.de

Schnellzugriff:

Folgt uns auf: