1. Was zuerst wichtig ist
Es gibt überraschend viele Situationen, in denen man C#-Quellcode verarbeiten möchte. Zum Beispiel diese Aufgaben.
Die Verwendung einer bestimmten API verbieten
Veraltete Schreibweisen maschinell finden
Eine Liste von Methoden und Klassen zusammenstellen
Abhängigkeiten im gesamten Projekt untersuchen
Standardcode zur Kompilierzeit generieren
Bei Build-Zeit vor Fehlgebrauch der eigenen Bibliothek warnen
Große Ersetzungen oder Migrationen sicher durchführen
In solchen Situationen ist man versucht, *.cs-Dateien einfach zu öffnen und mit Textsuche oder regulären Ausdrücken zu bearbeiten.
Aber C# ist keine Zeichenkette. Diese beiden Beispiele sehen sich zwar ähnlich, sind aber inhaltlich völlig verschieden.
Console.WriteLine("Hello");
MyCompany.Logging.Console.WriteLine("Hello");
Außerdem kann der Name Console auch einen anderen Typ bezeichnen.
using Console = MyCompany.Logging.Console;
Console.WriteLine("Hello");
Als Zeichenkette betrachtet sehen alle drei wie Console.WriteLine aus.
Aus Sicht des Compilers lässt sich aber ohne Namensauflösung nicht entscheiden, ob es sich um System.Console.WriteLine oder einen anderen Typ handelt.
Hier kommt Roslyn ins Spiel. Roslyn ist die Plattform, die die Informationen, über die die C#- und Visual-Basic-Compiler verfügen, als API für Anwendungen und Werkzeuge zugänglich macht.
Einfach gesagt: Mit Roslyn lässt sich C#-Code so behandeln.
Nicht als Zeichenkette, sondern als Syntax lesen
Nicht nach Aussehen, sondern nach Bedeutung lesen
Nicht als einzelne Datei, sondern als Projekt oder Solution lesen
Auf Basis des Gelesenen Warnungen, Korrekturvorschläge und generierten Code ausgeben
Dieser Artikel gibt einen Überblick über Roslyn insgesamt, über Syntax Tree, SemanticModel, Workspace, Analyzer, Source Generator und die praktischen Einsatzmöglichkeiten.
Der in diesem Artikel gezeigte Code ist als vollständiges, buildbares und ausführbares Beispielprojekt auf GitHub veröffentlicht (eine Bibliothek für Syntax Tree/SemanticModel, ein Analyzer, der vor DateTime.Now warnt, ein Source Generator, eine Demo, die eine ganze Solution analysiert, sowie Unit-Tests, die Fehlalarme und Erkennungslücken prüfen).
roslyn-dotnet-compiler-platform - komurasoft-blog-samples (GitHub)
Lesehinweise nach Zielsetzung
Der Artikel hat insgesamt 40 Kapitel. Allerdings gilt: Für Leser, die „einfach nur von vorhandenen Analyzern profitieren wollen“, und Leser, die „einen eigenen Analyzer schreiben wollen“, sind völlig unterschiedliche Kapitel relevant. Hier eine Übersicht nach Zielsetzung.
| Ziel | Zu lesende Kapitel | Anmerkung |
|---|---|---|
| Zunächst nur vorhandene Analyzer aktivieren und davon profitieren | Kapitel 13 → 14 → 29 | Kein Eigenbau nötig. Im Mittelpunkt steht .editorconfig |
| Verstehen, was Roslyn eigentlich ist | Kapitel 2 → 4 → 5 → 8 → 9 | Konzeptteil. Code muss kaum gelesen werden |
| Ein Untersuchungswerkzeug für Bestandscode schreiben | Kapitel 5 → 8 → 11 → 21 → 23 → 36 | Syntax Tree und Workspace spielen die Hauptrolle |
| Einen Analyzer für eigene Regeln bauen | Kapitel 12 → 15 → 28 → 29 → 31 | Ab hier wird man selbst zum „Bauenden“ |
| Auch Code Fixes ausliefern | Zusätzlich zu oben Kapitel 16 → 25 | |
| Einen Source Generator in Erwägung ziehen | Kapitel 17 → 18 → 19 → 20 → 30 | Wer zuerst Kapitel 19 und 20 („geeignet / ungeeignet“) liest, entscheidet schneller |
| Für Bestände auf .NET Framework einsetzen | Kapitel 32 → 33 | |
| Entscheiden, womit man anfängt | Kapitel 35 (Reihenfolge der Einführung) → Kapitel 39 (Praxis-Checkliste) |
Der Konzeptteil (Kapitel 2–11) und der Praxisteil (Kapitel 13–37) müssen nicht am Stück gelesen werden. Wer nichts selbst bauen will, fährt am kosteneffizientesten damit, nur die Kapitel 13 und 14 zu lesen und die .editorconfig einzurichten.
Der erste Schritt — Was man vorbereiten sollte
Was benötigt wird, hängt davon ab, „was gebaut werden soll“. Damit es nicht über den Fließtext verstreut und unübersichtlich wird, folgt zuerst eine Übersicht. Details werden in den jeweiligen Kapiteln behandelt.
| Was gebaut wird | Projektform | Wichtige NuGet-Pakete |
|---|---|---|
| Untersuchungswerkzeug auf Ebene einzelner Dateien | Gewöhnliche Konsolenanwendung (aktuelles .NET) | Microsoft.CodeAnalysis.CSharp |
| Untersuchungswerkzeug für die gesamte Solution | Gewöhnliche Konsolenanwendung (aktuelles .NET) | Zusätzlich zu oben Microsoft.CodeAnalysis.Workspaces.MSBuild, Microsoft.Build.Locator |
| Analyzer / Code Fix | Klassenbibliothek, netstandard2.0 |
Microsoft.CodeAnalysis.CSharp, Microsoft.CodeAnalysis.Analyzers |
| Source Generator | Klassenbibliothek, netstandard2.0 |
Microsoft.CodeAnalysis.CSharp |
| Tests für Analyzer / Generator | Testprojekt (aktuelles .NET) | Microsoft.CodeAnalysis.CSharp.CodeFix.Testing, Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing |
Beim Zielframework ist bei Analyzer und Source Generator besondere Vorsicht geboten. Da diese nicht in der eigenen Anwendung, sondern im Compiler auf Seiten des Nutzers geladen und ausgeführt werden, ist es üblich, netstandard2.0 als Ziel zu wählen. Auch die von Visual Studio bereitgestellte Vorlage heißt entsprechend „Analyzer with Code Fix (.NET Standard)“. Der Gedankengang zur Versionswahl wird in Kapitel 33 behandelt.
Wer in Visual Studio mit einer Vorlage beginnen möchte, sollte zuerst das .NET Compiler Platform SDK installieren. Setzen Sie im Visual Studio Installer auf der Registerkarte „Einzelne Komponenten“ ein Häkchen bei „.NET Compiler Platform SDK“ (im Abschnitt „Compiler, Buildtools und Runtimes“) oder wählen Sie es als optionale Komponente des Workloads „Entwicklung von Visual Studio-Erweiterungen“. Auch wenn Sie diesen Workload auswählen, wird die Komponente nicht automatisch mitinstalliert — setzen Sie das Häkchen also ausdrücklich.
Nach der Installation finden Sie über Datei > Neu > Projekt durch Suche nach „Analyzer“ die Vorlage „Analyzer with Code Fix (.NET Standard)“. Diese Vorlage erstellt auf einmal fünf Projekte: den eigentlichen Analyzer, den Code Fix, ein Projekt für die NuGet-Paketierung, ein Unit-Test-Projekt und ein VSIX-Projekt zum Testen des Verhaltens. Das VSIX-Projekt ist als Standard-Startprojekt festgelegt; beim Ausführen startet ein zweites Visual Studio, in dem der eigene Analyzer geladen ist.
2. Was Roslyn ist
Roslyn wird offiziell .NET Compiler Platform genannt. Es ist die Compiler-Implementierung für C# und Visual Basic und gleichzeitig eine Sammlung von APIs zum Bau von Code-Analysewerkzeugen.
Compiler wurden traditionell oft als solch eine „schwarze Box“ behandelt.
Quellcode wird eingegeben
Der Compiler verarbeitet ihn
DLL / EXE kommt heraus
Entwickler hatten normalerweise keinen Zugriff auf die Informationen, die intern im Compiler entstehen.
Tatsächlich wandelt ein Compiler aber nicht einfach nur Text in Maschinencode oder IL um, sondern erzeugt während der Kompilierung solche Informationen.
Diese Zeichenkette ist eine Klassendeklaration
Dieser Bezeichner ist eine lokale Variable
Dieser Methodenaufruf verweist auf diese Methode dieses Typs
Der Rückgabetyp dieses Ausdrucks ist string
Dieser Code ist ein Syntaxfehler
Diese Referenz verweist auf einen Typ in Assembly A
Diese using-Direktive wird tatsächlich nicht verwendet
Roslyn macht solche Informationen für Entwickler nutzbar. Deshalb ist Roslyn nicht nur ein Compiler, sondern eine Plattform zum Verstehen von Code.
3. Was man mit Roslyn machen kann
Mit Roslyn kann man hauptsächlich Folgendes tun.
C#/VB syntaktisch analysieren
Typen und Methoden semantisch auflösen
Kompilierinformationen abrufen
Ganze Projekte und Solutions analysieren
Eigene Analyzer erstellen
Code Fixes erstellen
Source Generatoren erstellen
Refactoring-Werkzeuge erstellen
Code generieren
Code transformieren
Praxisnäher formuliert sind das die Einsatzmöglichkeiten.
Verbotene APIs als Build-Warnung markieren
Fundstellen veralteter APIs auflisten
Namenskonventionen für async-Methoden prüfen
Fehlende Behandlung von IDisposable erkennen
Entwickler zur korrekten Nutzung des eigenen Frameworks anleiten
DTO- oder Mapping-Code zur Kompilierzeit generieren
Standardcode aus Konfigurationsdateien oder Attributen generieren
Migrationsuntersuchungen von .NET Framework nach .NET unterstützen
Wichtig an Roslyn ist, dass man Verarbeitung von C#-Quellcode auf demselben Fundament schreiben kann wie der Compiler selbst.
Wer C# mit regulären Ausdrücken oder einem selbstgeschriebenen Parser liest, stößt schnell an Grenzen. Zum Beispiel ist es nicht einfach, diese Elemente korrekt zu behandeln.
using-Aliase
Erweiterungsmethoden
partial class
partial method
global using
Nullable-Annotationen
generische Typen
Überladungsauflösung
bedingte Kompilierung
Präprozessor-Direktiven
Umschreiben unter Erhalt von Kommentaren und Leerraum
Roslyn stellt APIs bereit, um all das gemäß der C#-Sprachspezifikation zu behandeln.
4. Roslyn trennt „Syntax“ und „Bedeutung“
Wenn man Roslyn verstehen will, sollte man zuerst diese beiden Dinge trennen.
Syntax: Wie der Code geschrieben ist
Bedeutung: Worauf sich der Code bezieht
Betrachten Sie zum Beispiel diesen Code.
Console.WriteLine(message);
Syntaktisch betrachtet hat er diese Form.
Ausdrucksanweisung
Aufrufausdruck
Elementzugriffsausdruck
Bezeichner Console
Bezeichner WriteLine
Argument message
Allein daraus ergibt sich aber noch keine Bedeutung. Welchem Typ Console entspricht, welche Überladung WriteLine meint und welchen Typ message hat, lässt sich aus der Syntax allein nicht bestimmen.
Um die Bedeutung zu erschließen, braucht man diese Informationen.
Zustand der using-Direktiven
Referenzierte Assemblies
Typdefinitionen im selben Projekt
Referenzen auf andere Projekte
Typinferenz
Überladungsauflösung
Sprachversion
Nullable-Kontext
In Roslyn spiegelt sich dieser Unterschied auch in der API wider.
Syntax Tree : stellt die Syntax des Codes dar
SemanticModel : stellt dar, was die Syntax bedeutet
Compilation : stellt die für die Kompilierung nötigen Gesamtinformationen dar
Workspace : behandelt Solution, Projekt und Dokument
Wenn man diese Unterscheidung verinnerlicht, wird Roslyn deutlich übersichtlicher.
5. Was ein Syntax Tree ist
Der Syntax Tree ist ein Baum, der die syntaktische Struktur des Quellcodes darstellt. Nehmen wir zum Beispiel folgenden Code an.
class User
{
public string Name { get; set; }
public void Rename(string name)
{
Name = name;
}
}
Aus Sicht von Roslyn hat dieser Code grob diese Struktur.
CompilationUnit
ClassDeclaration: User
PropertyDeclaration: Name
MethodDeclaration: Rename
Parameter: name
Block
ExpressionStatement
AssignmentExpression
Der Syntax Tree ist keine simple zeilenweise Aufteilung des Texts, sondern eine als C#-Syntaxelemente strukturierte Darstellung — Klassen, Methoden, Eigenschaften, Ausdrücke, Anweisungen, Argumente, Operatoren.
Schauen wir uns ein einfaches Beispiel an.
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
var source = """
class User
{
public string Name { get; set; }
public void Rename(string name)
{
Name = name;
}
}
""";
var tree = CSharpSyntaxTree.ParseText(source);
var root = tree.GetCompilationUnitRoot();
var methods = root
.DescendantNodes()
.OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
Console.WriteLine(method.Identifier.Text);
}
Dieser Code sucht im Quellcode nach Methodendeklarationen und gibt die Methodennamen aus. In diesem Beispiel wird Rename ermittelt.
Wichtig ist, dass hier nicht per Textsuche nach void gesucht wird, sondern nach der C#-Syntax „Methodendeklaration“.
6. Node, Token und Trivia
Beim Umgang mit Syntax Trees begegnen einem häufig diese drei Begriffe.
SyntaxNode
SyntaxToken
SyntaxTrivia
SyntaxNode
SyntaxNode ist eine syntaktische Einheit. Zum Beispiel diese hier.
Klassendeklaration
Methodendeklaration
Eigenschaftsdeklaration
if-Anweisung
for-Anweisung
Zuweisungsausdruck
Aufrufausdruck
Lambda-Ausdruck
Alles, was syntaktisch weitere Unterelemente besitzt, ist ein Node.
SyntaxToken
SyntaxToken ist die kleinste syntaktische Einheit. Zum Beispiel etwa dies.
Schlüsselwort class
Schlüsselwort public
Bezeichner User
Bezeichner Rename
{ oder }
; oder ,
Zeichenkettenliteral
numerisches Literal
Ein Token ist ein Element am Ende des Syntaxbaums.
SyntaxTrivia
SyntaxTrivia sind Informationen, die für die eigentliche semantische Analyse nicht direkt relevant sind, aber für die originalgetreue Wiedergabe des Quellcodes benötigt werden. Zum Beispiel Dinge wie diese.
Leerraum
Zeilenumbruch
Kommentar
Präprozessor-Direktive
Dank dieser Trivia kann Roslyn Quellcode einschließlich Kommentaren und Leerraum mit hoher Genauigkeit verarbeiten.
Bei Codeformatierung, Refactoring und maschinellen Umschreibungen ist Trivia sehr wichtig.
Für den bloßen Aufbau eines AST scheint es akzeptabel, Kommentare wegzulassen. In der praktischen Codetransformation ist es jedoch wichtig, Kommentare und Zeilenumbrüche nicht zu zerstören.
Wie man sich den Baum ansehen kann
Bisher wurden Node/Token/Trivia in Textform erklärt — beim Syntax Tree lohnt es sich aber, ihn direkt am echten Code zu sehen. Dafür gibt es zwei Wege.
1. Der Syntax Visualizer von Visual Studio
Er lässt sich nutzen, sobald das in Kapitel 1 erwähnte .NET Compiler Platform SDK installiert ist.
Ansicht > Weitere Fenster > Syntax Visualizer
Setzt man im Editor den Cursor in den Code, wird der entsprechende Knoten im Baum hervorgehoben. Umgekehrt wird beim Auswählen eines Knotens im Baum die zugehörige Stelle im Editor markiert. Die Darstellung ist farblich unterschieden: SyntaxNode blau, SyntaxToken grün und SyntaxTrivia rot. Genau die drei in diesem Kapitel erklärten Arten sind als Farben direkt sichtbar.
Wer zusätzlich eine grafische Darstellung möchte, sollte im Visual Studio Installer unter „Einzelne Komponenten“ auch den DGML-Editor (im Abschnitt „Code-Tools“) installieren.
2. sharplab.io
sharplab.io ist ein reiner Browser-Playground für C#/VB/F#. Links schreibt man Code, rechts kann man zwischen dem Dekompilierungsergebnis nach C#, IL und dem nativen JIT-Code umschalten.
Um den Syntax Tree selbst zu betrachten, ist der Syntax Visualizer direkter, aber sharplab eignet sich dafür, zu prüfen, „wozu dieser syntaktische Zucker tatsächlich expandiert wird“. Man sieht direkt, dass eine using-Anweisung zu try/finally wird — das erleichtert es, den Gedanken aus Kapitel 4 zu verinnerlichen, dass Roslyn nicht das „Aussehen“, sondern die „Bedeutung“ behandelt.
Ein praktischer Tipp beim Schreiben von Analyzern: Sehen Sie zuerst im Syntax Visualizer nach, wie der Typname des Zielknotens (z. B. MemberAccessExpressionSyntax) und der SyntaxKind lauten, bevor Sie Code schreiben — das spart erheblichen Nacharbeitsaufwand. Auch dass der Analyzer in Kapitel 15 SyntaxKind.SimpleMemberAccessExpression angibt, ist eine Information, die sich auf diesem Weg ermitteln lässt.
7. Der Syntax Tree ist unveränderlich
Der Syntax Tree von Roslyn ist unveränderlich. Das heißt, ein einmal erzeugter Syntaxbaum wird nicht direkt umgeschrieben — stattdessen entsteht ein neuer Syntaxbaum mit den Änderungen.
Möchte man beispielsweise einen Methodennamen ändern, wird nicht das bestehende MethodDeclarationSyntax-Objekt an Ort und Stelle verändert.
var newMethod = oldMethod.WithIdentifier(
SyntaxFactory.Identifier("NewName"));
Stattdessen wird auf diese Weise ein neuer Knoten erzeugt.
Unveränderlichkeit bringt mehrere Vorteile mit sich.
Leicht in mehreren Threads verwendbar
Im IDE bearbeitete Snapshots lassen sich sicher handhaben
Diffs lassen sich leicht erzeugen
Zustand vor und nach der Änderung lässt sich leicht vergleichen
Anfangs mag das etwas umständlich wirken. In einer Welt jedoch, in der wie bei IDE, Build, Analyzer und Source Generator mehrere Prozesse gleichzeitig auf denselben Code zugreifen, ist Unveränderlichkeit ein großer Vorteil.
8. Was das SemanticModel ist
Der Syntax Tree allein zeigt nur das Aussehen des Codes. Um die Bedeutung zu erfahren, verwendet man das SemanticModel.
Betrachten wir zum Beispiel diesen Code.
Console.WriteLine("Hello");
Aus dem Syntax Tree geht hervor, dass es die Bezeichner Console und WriteLine gibt. Ob sie aber System.Console.WriteLine(string?) oder eine Methode eines anderen Typs bezeichnen, geht daraus nicht hervor.
Mit dem SemanticModel lässt sich ermitteln, „auf welches Symbol dieser Syntaxknoten aufgelöst wurde“.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
var source = """
using System;
class Program
{
static void Main()
{
Console.WriteLine("Hello");
}
}
""";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create(
assemblyName: "Sample",
syntaxTrees: new[] { tree },
references: new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
});
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetCompilationUnitRoot();
var invocation = root
.DescendantNodes()
.OfType<InvocationExpressionSyntax>()
.First();
var symbolInfo = semanticModel.GetSymbolInfo(invocation);
var method = (IMethodSymbol?)symbolInfo.Symbol;
Console.WriteLine(method?.ContainingType.ToDisplayString());
Console.WriteLine(method?.Name);
So lässt sich ermitteln, auf welche Methode Console.WriteLine tatsächlich aufgelöst wurde.
Dass man nicht nur die Syntax, sondern das Namensauflösungsergebnis des Compilers nutzen kann, ist die Stärke von Roslyn.
9. Was ein Symbol ist
In Roslyn werden Typen, Methoden, Eigenschaften, Felder, Argumente und lokale Variablen als Symbol behandelt.
Hier die wichtigsten Schnittstellen.
INamedTypeSymbol : Klasse, Struktur, Schnittstelle usw.
IMethodSymbol : Methode
IPropertySymbol : Eigenschaft
IFieldSymbol : Feld
IParameterSymbol : Argument
ILocalSymbol : lokale Variable
INamespaceSymbol : Namespace
Ein Symbol repräsentiert nicht das Aussehen im Quellcode, sondern die vom Compiler aufgelöste Bedeutung.
Diese beiden Codebeispiele sehen zum Beispiel unterschiedlich aus.
System.Console.WriteLine("Hello");
using System;
Console.WriteLine("Hello");
Verweisen aber beide auf dasselbe System.Console.WriteLine, lassen sie sich in der semantischen Analyse von Roslyn als dasselbe Methodensymbol behandeln.
Dank dieser Eigenschaft lassen sich solche Fragen beantworten.
Ist dieser Aufruf wirklich eine API, die wir intern verboten haben?
Implementiert dieser Typ eine bestimmte Schnittstelle?
Ist diese Methode async?
Ist dieser Rückgabewert nullable?
Ist dieses Attribut tatsächlich angebracht?
Erbt diese Klasse von einer bestimmten Basisklasse?
Das ist nicht bloße Textsuche, sondern Analyse auf Basis der Entscheidungen des Compilers.
10. Was eine Compilation ist
Die Compilation bündelt die Informationen, die zum Kompilieren eines C#- oder Visual-Basic-Programms erforderlich sind.
Konkret enthält sie diese Informationen.
Die Menge der SyntaxTree-Objekte
Referenzierte Assemblies
Kompilieroptionen
Sprachversion
Vordefinierte Symbole
Typ- und Elementinformationen
Diagnoseinformationen
Um eine einzelne Datei nur syntaktisch zu lesen, genügt der SyntaxTree. Für Typauflösung oder Referenzauflösung braucht man jedoch die Compilation.
Das gilt zum Beispiel, wenn man Folgendes wissen möchte.
Welchem Typ gehört die Methode, die dieser Aufruf meint?
Implementiert diese Klasse IDisposable?
Auf welchen Attributtyp verweist dieses Attribut tatsächlich?
Welchen Rückgabetyp hat dieser Ausdruck?
Kompilierfehler oder -warnungen abrufen
Das lässt sich nicht allein aus der Syntax bestimmen. Man muss auch die Referenzen und Kompilieroptionen des Projekts einbeziehen.
11. Was ein Workspace ist
Möchte man nicht nur eine einzelne Datei, sondern eine ganze Solution oder ein ganzes Projekt behandeln, verwendet man den Workspace.
Der Workspace arbeitet mit diesen Einheiten.
Solution
Project
Document
Ein Beispiel: die gesamte Solution einlesen, alle Projekte laden und alle Dokumente analysieren.
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
MSBuildLocator.RegisterDefaults();
using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync("Sample.sln");
foreach (var project in solution.Projects)
{
Console.WriteLine(project.Name);
foreach (var document in project.Documents)
{
var root = await document.GetSyntaxRootAsync();
Console.WriteLine($" {document.Name}: {root?.DescendantNodes().Count()} nodes");
}
}
Ein so gebautes Werkzeug lässt sich für die Untersuchung bestehender Codebasen und für Migrationsunterstützung nutzen.
Zum Beispiel für diese Zwecke.
Eine CSV-Liste bestimmter API-Aufrufe erzeugen
Fundstellen veralteter Namespaces auflisten
Eine Liste öffentlicher APIs erstellen
Abhängigkeiten zwischen Projekten untersuchen
Verstöße gegen Codekonventionen in großen Solutions untersuchen
Maschinelle Codetransformationen durchführen
Ein Analyzer ist ein Mechanismus, der eng mit IDE und Build verzahnt läuft. Eine Konsolenanwendung mit Workspace eignet sich dagegen für Untersuchungen und Massenmigrationen.
Diese beiden ähneln sich, sollten aber jeweils passend eingesetzt werden.
12. Typische Nutzungsformen von Roslyn
Roslyn lässt sich grob in vier Einsatzformen unterteilen.
1. Als Bibliothek verwenden
2. Einen Analyzer bauen
3. Einen Code Fix bauen
4. Einen Source Generator bauen
Jede hat einen anderen Zweck.
Als Bibliothek verwenden
Man ruft die Roslyn-API aus einer selbstgeschriebenen Konsolenanwendung oder einem internen Werkzeug auf.
Geeignete Einsatzgebiete sind etwa diese.
Untersuchung der Codebasis
Massentransformation
Metrikerfassung
Migrationsunterstützung
Berichtserstellung
In dieser Form lässt sich das Werkzeug jederzeit nach Bedarf ausführen. Da es nicht während der IDE-Eingabe laufen muss, sind auch etwas aufwendigere Verarbeitungen akzeptabel.
Einen Analyzer bauen
Ein Analyzer ist ein Mechanismus, der Code analysiert und Warnungen oder Fehler ausgibt.
Man kann zum Beispiel solche Regeln erstellen.
DateTime.Now nicht verwenden, sondern DateTimeOffset.UtcNow
async-Methodennamen müssen auf Async enden
Initialisierungs-APIs der Bibliothek nicht in falscher Reihenfolge aufrufen
Einen bestimmten Namespace in neuem Code nicht verwenden
Verwendung von Task.Result / Wait verbieten
Ein Analyzer lässt sich in Visual Studio und beim Build ausführen. Team-Konventionen und die korrekte Nutzung von Bibliotheken lassen sich maschinell erkennen, statt sich auf das Gedächtnis der Reviewer zu verlassen.
Einen Code Fix bauen
Ein Code Fix ist ein Mechanismus, der zu einem vom Analyzer gefundenen Problem einen Korrekturvorschlag liefert.
Man kann sich das gut anhand der Korrektur vorstellen, die man in Visual Studio über das Glühbirnen-Symbol anwenden kann.
Angenommen, ein Analyzer erkennt diesen Code.
DateTime.Now
Der Code Fix kann diese Korrektur vorschlagen.
DateTimeOffset.UtcNow
Die Stärke des Code Fix liegt darin, nicht nur „eine Warnung auszugeben“, sondern die „sichere Korrektur“ zu automatisieren.
Einen Source Generator bauen
Ein Source Generator generiert zur Kompilierzeit Code und fügt diesen Code derselben Kompilierung hinzu.
Beispiele für solche Einsatzzwecke.
Aus attributierten Klassen Standardcode generieren
Aus Konfigurationsdateien typsichere Zugriffe generieren
DTO-Mapping-Code generieren
Serialisierer-Code generieren
Umwandlungscode zwischen enum und string generieren
Routing- oder DI-Registrierungscode generieren
Verarbeitung, die zur Laufzeit Informationen per Reflection sammelte, lässt sich mitunter durch zur Kompilierzeit generierten Code ersetzen. Das kann die Startkosten senken und die Kompatibilität mit AOT verbessern.
13. Ein Analyzer lässt sich als „automatisches Code-Review“ nutzen
In der Praxis lässt sich ein Analyzer gut als „automatisches Code-Review“ verstehen.
Bei Code-Reviews kommt es vor, dass jedes Mal dieselben Hinweise auftauchen.
Diese API bitte nicht verwenden
Dieser Methodenname entspricht nicht der Konvention
Dieses catch schluckt den Fehler
Diese Null-Prüfung ist überflüssig
Dieser Aufruf hat ein Performance-Problem
Wenn ein Mensch das jedes Mal anmerkt, lässt sich ein Teil davon durch einen Analyzer ersetzen.
Besonders geeignet für Analyzer sind Regeln wie diese.
Eindeutig als gut oder schlecht beurteilbar
Wenige Ausnahmen
Klare Korrekturrichtung
Vom ganzen Team gewünscht
Häufig im Review
Darf den Build blockieren
Umgekehrt gibt es auch Fälle, die für einen Analyzer ungeeignet sind.
Kontextabhängig und schwer zu beurteilen
Designentscheidung erforderlich
Zu viele Ausnahmen
Meinungen gehen auseinander
Zu viele Warnungen, sodass niemand mehr hinschaut
Ein Analyzer ist ein starkes Werkzeug. Gerade weil er stark ist, verschlechtert er die Entwicklererfahrung, wenn man zu viele einführt. Am besten beginnt man mit wenigen, wichtigen Regeln.
14. Mit den im .NET SDK enthaltenen Analyzern beginnen
Bevor man einen eigenen Analyzer schreibt, ist es sinnvoll, zunächst die im .NET SDK enthaltenen Analyzer zu prüfen.
In Projekten ab .NET 5 ist die .NET-Codeanalyse standardmäßig aktiviert.
Häufig sieht man diese beiden Diagnose-ID-Reihen.
CAxxxx : Codequalität, Zuverlässigkeit, Performance, Sicherheit usw.
IDExxxx: Codestil, IDE-Unterstützung usw.
Der Schweregrad eines Analyzers lässt sich über .editorconfig anpassen.
# Beispiel: nicht verwendete usings als Warnung
dotnet_diagnostic.IDE0005.severity = warning
# Beispiel: CA2000 als Fehler
dotnet_diagnostic.CA2000.severity = error
Auch in der Projektdatei lässt sich die Aktivierung oder Verschärfung vornehmen.
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
Führt man das in einem bestehenden Projekt ein, kann es anfangs zu einer großen Zahl an Warnungen kommen. In diesem Fall ist es besser, nicht alles sofort zu Fehlern zu machen, sondern schrittweise vorzugehen.
Zunächst die Gesamtzahl der Warnungen erfassen
Festlegen, dass neuer Code sie nicht vermehrt
Nur wichtige Regeln als warning setzen
Nur wirklich einzuhaltende Regeln als error setzen
Bestehende Verstöße planmäßig abbauen
Einen eigenen Analyzer sollte man als Ergänzung „unternehmenseigener Regeln“ auf diesem Fundament betrachten.
15. Die minimale Vorstellung eines Analyzers
Ein Analyzer sucht bestimmte Syntax oder Symbole und meldet ein Diagnostic.
Betrachten wir zum Beispiel einen Analyzer, der vor der Verwendung von DateTime.Now warnt.
In echtem Produktionscode müssen Typauflösung und Ausnahmefälle sorgfältiger behandelt werden, aber die minimale Vorstellung sieht so aus.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class NoDateTimeNowAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new(
id: "CMP001",
title: "DateTime.Now nicht direkt verwenden",
messageFormat: "Verwenden Sie statt DateTime.Now je nach Anwendungsfall DateTimeOffset.UtcNow oder eine ähnliche Alternative",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(
AnalyzeMemberAccess,
SyntaxKind.SimpleMemberAccessExpression);
}
private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
{
var memberAccess = (MemberAccessExpressionSyntax)context.Node;
if (memberAccess.Name.Identifier.Text != "Now")
{
return;
}
var symbol = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol;
if (symbol is not IPropertySymbol propertySymbol)
{
return;
}
if (propertySymbol.Name == "Now" &&
propertySymbol.ContainingType.ToDisplayString() == "System.DateTime")
{
var diagnostic = Diagnostic.Create(Rule, memberAccess.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
Wichtig an diesem Beispiel ist, dass hier nicht einfach als Text nach DateTime.Now gesucht wird.
Mit dem SemanticModel wird geprüft, ob tatsächlich System.DateTime.Now gemeint ist.
Dadurch wird ein Fehlalarm bei etwas anderem wie diesem seltener.
MyCompany.DateTime.Now
Bei Analyzern ist dieser Ablauf üblich: über Syntax grenzt man Kandidaten schnell ein, über semantische Analyse bestätigt man sie präzise.
Mit Syntax schnell Kandidaten suchen
Mit SemanticModel präzise beurteilen
Mit Diagnostic Ort und Meldung melden
16. Der Code Fix liefert auch die „Art der Korrektur“
Der Analyzer findet ein Problem, der Code Fix zeigt, wie man es behebt.
Wird zum Beispiel DateTime.Now erkannt, lassen sich solche Korrekturvorschläge anbieten.
Durch DateTimeOffset.UtcNow ersetzen
Durch eine Abstraktion wie IClock.Now ersetzen
Ein Code Fix muss allerdings sorgfältig entworfen werden. Es ist nicht immer richtig, DateTime.Now grundsätzlich durch DateTimeOffset.UtcNow zu ersetzen. Will man die lokale Zeit anzeigen oder eine Zeit zum Speichern und Vergleichen behandeln, unterscheiden sich der passende Typ und der Umgang mit der Zeitzone.
Ein Code Fix eignet sich deshalb, wenn diese Bedingungen erfüllt sind.
Die Bedeutung nach der Korrektur ist eindeutig
Die Nebenwirkungen sind gering
Die Umwandlung lässt sich maschinell sicher durchführen
Der Mensch kann sie leicht nachvollziehen
Zum Beispiel passt diese Art von Korrektur gut zu einem Code Fix.
Einen alten API-Namen durch den neuen ersetzen
Eine fehlende using-Direktive hinzufügen
Namen gemäß Namenskonvention ändern
Ein Attribut hinzufügen
Ein überflüssiges Argument entfernen
Bei Korrekturen, die eine Designentscheidung erfordern, ist es dagegen manchmal besser, nur eine Warnung auszugeben, statt automatisch zu korrigieren.
17. Der Source Generator ist „Codegenerierung zur Kompilierzeit“
Ein Source Generator läuft zur Kompilierzeit und fügt den generierten C#-Code derselben Kompilierung hinzu.
Als Ablauf kann man sich das so vorstellen.
Den Quellcode des Nutzers lesen
Attribute oder Typdefinitionen untersuchen
Den benötigten C#-Code generieren
Den generierten Code dem Kompilierziel hinzufügen
Ein einfaches Source-Generator-Beispiel.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public sealed class BuildInfoGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(static ctx =>
{
var source = """
namespace Generated;
public static class BuildInfo
{
public static string Tool => "Roslyn Source Generator";
}
""";
ctx.AddSource(
"BuildInfo.g.cs",
SourceText.From(source, Encoding.UTF8));
});
}
}
In einem Projekt, das diesen Generator referenziert, lässt sich dieser Typ verwenden, obwohl keine entsprechende Quelldatei geschrieben wurde.
Console.WriteLine(Generated.BuildInfo.Tool);
Ein Source Generator schreibt bestehenden Code des Nutzers nicht um. Er kann nur zusätzlichen Quellcode generieren und ihn an der Kompilierung teilnehmen lassen.
Man sollte also so denken.
Kein Werkzeug zur Umwandlung bestehenden Codes
Ein Werkzeug, das bestehenden Code betrachtet und daraus zusätzlichen Code erzeugt
Für eine Massenumschreibung bestehenden Codes sollte man statt eines Source Generators ein Roslyn-basiertes Migrationswerkzeug oder einen Code Fix in Betracht ziehen.
18. Wie man einen Source Generator referenziert
Referenziert man während der Entwicklung ein Generator-Projekt aus einem anderen Projekt, unterscheidet sich das von einer gewöhnlichen Bibliotheksreferenz.
Der Grund ist, dass ein Generator nicht als zur Laufzeit referenzierte Bibliothek, sondern als Analyzer zur Kompilierzeit geladen wird.
Bei einer Projektreferenz gibt man dies an.
<ItemGroup>
<ProjectReference Include="..\BuildInfoGenerator\BuildInfoGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
Durch ReferenceOutputAssembly="false" wird verhindert, dass die DLL des Generators als gewöhnliche Referenzassembly behandelt wird.
Auch bei der Verteilung als NuGet-Paket muss die Ablage so erfolgen, dass die Bibliothek als Analyzer/Source Generator geladen wird.
Ein Source Generator ist praktisch, hat aber einen anderen Lebenszyklus als eine gewöhnliche Bibliothek.
Gewöhnliche Bibliothek: wird zur Laufzeit von der Anwendung verwendet
Source Generator: wird zur Kompilierzeit vom Compiler verwendet
Diesen Unterschied sollte man sich bewusst machen.
19. Wofür sich ein Source Generator eignet
Ein Source Generator ist nicht dazu da, alles Mögliche zu generieren.
Geeignet ist er für Code wie diesen.
Von Hand geschrieben langweilig und fehleranfällig
Aus Eingabeinformationen maschinell festgelegt
Das Generierungsergebnis ist gut lesbar
Reduziert Reflection zur Laufzeit
Verbessert die Kompatibilität mit AOT und Trimming
Erhöht die Typsicherheit
Beispiele.
Metadaten für JSON-Serialisierung
DI-Registrierungscode
Zugriffscode für Konfigurationswerte
API-Clients
Umwandlungscode für enum
Typgenerierung aus SQL- oder CSV-Definitionen
Hilfscode für INotifyPropertyChanged
Ist der generierte Code jedoch zu komplex, wird er im Fehlerfall schwer nachzuvollziehen.
Beim Einsatz eines Source Generators sollte man auf Folgendes achten.
Den generierten Code einsehbar machen
Namen des generierten Codes stabil halten
Das Generierungsergebnis deterministisch machen
Diagnostics bei Fehlern verständlich gestalten
Verhindern, dass eine kleine Eingabeänderung riesige Diffs erzeugt
Generierter Code darf nicht wie Magie wirken. Wichtig ist, Code auszugeben, den künftige Maintainer lesen können.
20. Wofür sich ein Source Generator nicht eignet
Es gibt auch Verarbeitung, für die sich ein Source Generator nicht eignet.
Verarbeitung, die vom Laufzeitzustand abhängt
Verarbeitung, die Netzwerkzugriff benötigt
Verarbeitung, die vom aktuellen Wert eines externen Dienstes abhängt
Verarbeitung, die jedes Mal ein anderes Ergebnis liefert
Umschreiben bestehenden Quellcodes
Analyse der gesamten, riesigen Solution
Da ein Generator zur Kompilierzeit läuft, verschlechtert ein langsamer Generator die Buildzeit und die IDE-Erfahrung.
Zudem verursacht ein umgebungsabhängiger Generator solche Probleme.
Läuft auf dem PC des Entwicklers, aber nicht in der CI
Läuft in der CI, aber nicht auf einem anderen Betriebssystem
Das Ergebnis ändert sich je nach Cache-Zustand
Der Build schlägt bei Netzwerkausfall fehl
Ein Source Generator sollte möglichst rein gehalten werden.
Eingabe: Quellcode, AdditionalFiles, AnalyzerConfigOptions
Ausgabe: generierter C#-Code, Diagnostic
Je klarer diese Beziehung ist, desto stabiler wird der Generator.
21. Ein Codeuntersuchungswerkzeug mit Roslyn
Roslyn wird nicht nur für Analyzer und Source Generatoren eingesetzt. Auch der Einsatz aus einem selbstgeschriebenen Konsolenwerkzeug heraus ist in der Praxis nützlich.
Es gibt zum Beispiel Anforderungen wie diese.
Fundstellen veralteter APIs auflisten
Die Anzahl öffentlicher Klassen pro Projekt zählen
Klassen mit einem bestimmten Attribut als CSV ausgeben
Genutzte Namespaces in einer riesigen Solution untersuchen
Vor einer .NET-Framework-Migration Windows-abhängige APIs ermitteln
In solchen Fällen ist es oft handlicher, statt eines Analyzers ein einmalig oder regelmäßig ausgeführtes Untersuchungswerkzeug zu bauen.
Als Beispiel eine einfache Vorstellung davon, wie man öffentliche Klassen in einer Solution auflistet.
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
MSBuildLocator.RegisterDefaults();
using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(args[0]);
foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
var root = await document.GetSyntaxRootAsync();
if (root is null)
{
continue;
}
var classes = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Where(c => c.Modifiers.Any(m => m.Text == "public"));
foreach (var cls in classes)
{
Console.WriteLine($"{project.Name},{document.FilePath},{cls.Identifier.Text}");
}
}
}
Dieses Beispiel betrachtet nur die Syntax. Möchte man etwa „öffentliche Klassen, die von einer bestimmten Basisklasse erben“ ermitteln, muss man mit dem SemanticModel die Vererbungsbeziehung des Typs betrachten.
Genügen Name und Form: Syntax
Werden Typ oder Referenzziel benötigt: SemanticModel
Soll das ganze Projekt behandelt werden: Workspace
Diese Unterscheidung ist die Grundlage.
22. Der Unterschied zu regulären Ausdrücken
Reguläre Ausdrücke sind praktisch, eignen sich aber nicht dafür, die Bedeutung von C#-Code zu erfassen.
Betrachten wir zum Beispiel diesen Code.
// Console.WriteLine("debug");
Sucht man mit einem regulären Ausdruck nach Console.WriteLine, erfasst man womöglich auch Zeichenketten innerhalb von Kommentaren.
Auch solche Zeichenkettenliterale gibt es.
var text = "Console.WriteLine";
Oder man kann einen Zeilenumbruch einfügen.
Console
.WriteLine("Hello");
Außerdem kann man einen Alias verwenden.
using C = System.Console;
C.WriteLine("Hello");
Das mit regulären Ausdrücken korrekt zu behandeln, ist schwierig.
Mit Roslyn lassen sich Kommentare, Zeichenkettenliterale, syntaktische Methodenaufrufe und tatsächlich aufgelöste Methoden sauber unterscheiden.
Natürlich reichen für eine einfache Untersuchung manchmal grep oder ripgrep aus. Sollen jedoch Designentscheidungen oder automatische Korrekturen auf den Ergebnissen aufbauen, ist der Einsatz von Roslyn sicherer.
Für eine grobe Suche: Textsuche
Für eine korrekte C#-Beurteilung: Roslyn
23. Roslyn zur Untersuchung von Bestandscode einsetzen
Bei der Migration von .NET Framework zum aktuellen .NET braucht man zuerst eine „Bestandsaufnahme“. Dabei ist Roslyn nützlich.
Zum Beispiel für solche Untersuchungen.
Liste der Abhängigkeiten von System.Web
Liste von Code, der App.config/Web.config voraussetzt
Fundstellen Windows-Forms-/WPF-spezifischer APIs
Fundstellen von Remoting/BinaryFormatter
Vorhandensein von COM-Referenzen
Liste von P/Invoke-Aufrufen
Nicht asynchronisierte I/O-Verarbeitung
Fundstellen veralteter kryptografischer APIs
Auch eine einfache Textsuche liefert Kandidaten. Mit Roslyn lässt sich aber eine Liste auf Basis der als Typ oder Methode aufgelösten Ergebnisse erstellen.
Sucht man zum Beispiel nur nach der Zeichenkette BinaryFormatter, erfasst man auch Kommentare und Dokumentation.
Untersucht man mit Roslyn die Verwendung des Typs System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, erhält man präzisere Kandidaten.
Bei der Migrationsarbeit muss man nicht von Anfang an einen perfekten Analyzer bauen. Es hat bereits einen Wert, zunächst nur mit einem Untersuchungswerkzeug eine solche CSV-Datei auszugeben.
Project,File,Line,Symbol,Kind
Legacy.Web,Controllers/HomeController.cs,42,System.Web.HttpContext.Current,Property
Legacy.Core,Serialization/OldStore.cs,18,System.Runtime.Serialization.Formatters.Binary.BinaryFormatter,Type
Mit einer solchen Liste lässt sich ein Migrationsplan leichter erstellen.
24. Roslyn aus Sicht von Bibliotheksentwicklern
Roslyn ist nicht nur für Anwendungsentwickler nützlich, sondern auch für Bibliotheksentwickler. Bibliotheken haben eine korrekte Art der Verwendung.
Zum Beispiel Regeln wie diese.
Eine Initialisierungsmethode muss zuerst aufgerufen werden
Ein bestimmtes Attribut muss gesetzt werden
Dispose muss aufgerufen werden
Eine bestimmte Optionsangabe ist gefährlich
Eine veraltete API soll in neuem Code nicht verwendet werden
Vermittelt man das nur über Dokumentation, übersehen Nutzer es mitunter.
Legt man einen Analyzer dem NuGet-Paket der Bibliothek bei, lässt sich eine Warnung direkt im Code des Nutzers ausgeben.
Angenommen, es gibt eine eigene Bibliothek Company.Messaging; damit lässt sich zum Beispiel dieser Fehlgebrauch erkennen.
var client = new MessageClient();
client.Send(message); // Send wird aufgerufen, bevor Configure aufgerufen wurde
Der Analyzer kann eine solche Warnung ausgeben.
CMP1001: Rufen Sie Configure auf, bevor Sie MessageClient.Send aufrufen
Zusätzlich kann ein Code Fix Korrekturvorschläge und Beispielcode anbieten. Das verbessert die Nutzererfahrung der Bibliothek.
Was in der Dokumentation steht, dem Nutzer direkt im Editor vermitteln
Dieser Gedanke ist einer der großen Werte von Roslyn.
25. Überlegungen zur Verteilung eines Analyzers über NuGet
Ein Analyzer lässt sich als NuGet-Paket verteilen. Man muss ihn dabei jedoch getrennt von einer gewöhnlichen Laufzeitbibliothek betrachten. Ein Analyzer wird nicht zur Laufzeit der Anwendung benötigt, sondern zur Build-Zeit oder im IDE verwendet.
Beim Paketdesign sollte man daher Folgendes bedenken.
Laufzeitbibliothek und Analyzer im selben Paket bündeln oder nicht
Den Analyzer in ein eigenes Paket auslagern
Standardmäßig eine Warnung ausgeben oder nicht
Wie der Schweregrad gewählt wird
Ob sich der Analyzer über .editorconfig steuern lässt
Bestehende Nutzer nicht plötzlich mit einer Flut von Warnungen konfrontieren
Für den internen Gebrauch akzeptiert man mitunter auch etwas strengere Regeln leichter.
Bei der Verteilung als öffentliche Bibliothek muss man darauf achten, den Build der Nutzer nicht plötzlich zu brechen.
Oft ist es handlicher, zunächst mit Info oder Warning zu beginnen und es dem Nutzer je nach Bedarf zu überlassen, auf Error hochzustufen.
26. Roslyn und IDE-Funktionen
In Visual Studio und anderen .NET-Entwicklungsumgebungen ist der Gedanke von Roslyn tief in die IDE-Funktionen eingebunden.
Zum Beispiel bei Funktionen wie diesen.
IntelliSense
Gehe zu Definition
Alle Referenzen suchen
Umbenennen
Methode extrahieren
Schnellaktionen
Codestil-Warnungen
Erkennung nicht verwendeter usings
Diese lassen sich nicht allein durch Textsuche realisieren. Beim Umbenennen darf man zum Beispiel nicht versehentlich ein anderes Symbol mit demselben Namen ändern.
class User
{
public string Name { get; set; }
}
class Product
{
public string Name { get; set; }
}
Möchte man User.Name ändern, darf Product.Name nicht mitgeändert werden. Das erfordert eine Unterscheidung nicht nur nach Syntax, sondern nach Symbol.
Die API von Roslyn ist das Fundament, um solche IDE-artigen Funktionen auch in eigenen Werkzeugen anzuwenden.
27. Hinweise zur Performance
Roslyn ist mächtig, aber wer schwergewichtige Verarbeitung schreibt, wird natürlich langsamer. Besonders Analyzer und Source Generatoren können während der Eingabe des Entwicklers oder während des Builds laufen.
Deshalb sollte man auf Folgendes achten.
Unnötige Abfragen des SemanticModel vermeiden
Zuerst mit Syntax Kandidaten eingrenzen, dann semantisch analysieren
Datei-I/O vermeiden
Keinen Netzwerkzugriff durchführen
Aufwendige Reflection vermeiden
Abbruchanforderungen respektieren
Parallele Ausführung im Blick behalten
Keine Analyse der gesamten Solution in einen Analyzer packen
Beim Analyzer sollte man die in Initialize registrierten Ziele so weit wie möglich eingrenzen.
Ein schlechtes Beispiel.
Alle SyntaxNodes betrachten und intern mit vielen if-Anweisungen beurteilen
Eine gute Richtung.
Nur den benötigten SyntaxKind registrieren
Zuerst grob nach Name oder Form eingrenzen
Nur bei Bedarf mit dem SemanticModel abschließend beurteilen
Ein Analyzer läuft möglicherweise dauerhaft in der Entwicklungsumgebung des Nutzers. Deshalb ist nicht nur Genauigkeit, sondern auch Leichtgewichtigkeit eine Qualität.
28. Das Design eines Diagnostic
Bei einem Analyzer genügt es nicht, einfach eine Warnung auszugeben. Der Entwickler, der sie sieht, sollte Folgendes erkennen können.
Was das Problem ist
Warum es ein Problem ist
Wo korrigiert werden muss
Wie korrigiert werden muss
Ob es Ausnahmen gibt
Ein Beispiel für eine schlechte Meldung.
CMP001: Verboten
Daraus wird nicht klar, was falsch ist.
Ein Beispiel für eine gute Richtung.
CMP001: DateTime.Now hängt von der lokalen Zeit der Ausführungsumgebung ab. Verwenden Sie für Speicherung und Vergleich DateTimeOffset.UtcNow oder einen Zeit-Provider.
Auch die Diagnostic-ID sollte man planen.
CMP0001-CMP0999: gemeinsame Regeln
CMP1000-CMP1999: Regeln von Bibliothek A
CMP2000-CMP2999: Migrationsunterstützungsregeln
Lässt sich eine Dokumentationsseite bereitstellen, ist es auch sinnvoll, im DiagnosticDescriptor HelpLinkUri zu setzen.
Eine Warnung ist Kommunikation mit dem Entwickler. Ist die Meldung schlampig, verliert die Regel selbst an Vertrauen.
29. Das Design des Schweregrads
Den Schweregrad eines Analyzers sollte man sorgfältig festlegen. Die typischen Stufen sind diese.
Hidden / Silent
Info
Suggestion
Warning
Error
In der Praxis ist es oft besser, nicht sofort Error zu setzen. Besonders bei viel Bestandscode blockiert eine sofortige Einstufung als Error die Einführung.
Realistisch ist eine schrittweise Einführung wie diese.
1. Zunächst mit Warning einführen
2. Die Warnungszahl in der CI sichtbar machen
3. Keine neuen Verstöße mehr zulassen
4. Nur wichtige Regeln als Error setzen
5. Einen Plan zum Abbau bestehender Verstöße aufstellen
Ziel eines Analyzers ist nicht, Entwickler zu behindern, sondern die Codequalität ohne Zwang zu erhöhen.
30. Debuggen eines Source Generators
Ein Source Generator läuft an einer anderen Stelle als eine gewöhnliche Anwendung, weshalb das Debuggen einige Besonderheiten hat.
Grundsätzlich untersucht man mit Methoden wie diesen.
Den generierten Quellcode ansehen
Ein Diagnostic ausgeben
Tests schreiben
Bei Bedarf den Debugger anhängen
In SDK-Stil-Projekten lässt sich mit einer Einstellung zur Ausgabe der generierten Dateien leichter prüfen.
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
Dadurch lässt sich die generierte .g.cs-Datei leichter einsehen.
$(BaseIntermediateOutputPath) verweist üblicherweise auf das Verzeichnis obj/.
Gibt man wie bei Generated einen Pfad direkt im Projektverzeichnis an, werden in SDK-Stil-Projekten standardmäßig alle **/*.cs-Dateien in die Kompilierung einbezogen. Dadurch wird die bereits generierte .g.cs-Datei beim nächsten Build erneut als gewöhnlicher Quellcode aufgenommen, was zu doppelten Typ- oder Elementfehlern führen kann.
Wenn unbedingt direkt im Projektverzeichnis ausgegeben werden soll, muss man die Datei mit <Compile Remove="Generated/**/*.cs" /> explizit von der Kompilierung ausschließen.
Beim Testen eines Generators ist es üblich, Eingabecode und Generierungsergebnis zu vergleichen.
Eingabequellcode vorbereiten
Den Generator ausführen
Den generierten Quellcode prüfen
Das erwartete Diagnostic prüfen
Ein Source Generator lässt sich durch manuelle Prüfung allein schnell kaputtmachen. Je komplexer die Generierungslogik wird, desto wichtiger werden Tests.
Reihenfolge der Untersuchung, wenn nichts generiert zu werden scheint
Wenn ein Source Generator „scheinbar nicht funktioniert“, liegt die Ursache häufig nicht in der Generierungslogik selbst, sondern in der Art der Referenzierung oder im IDE-Cache. Nach Symptom sortiert, was man zuerst prüfen sollte.
| Symptom | Was zuerst zu prüfen ist |
|---|---|
| Der Build läuft durch, aber der eigentlich generierte Typ wird nicht gefunden | Die Referenzierungsart des Generator-Projekts. Sind OutputItemType="Analyzer" und ReferenceOutputAssembly="false" gesetzt (Kapitel 18)? |
Unter obj/ erscheint keine .g.cs-Datei |
Wurde EmitCompilerGeneratedFiles aktiviert? Erscheint trotzdem nichts, wird der Generator selbst nicht aufgerufen |
| Der Build auf der Kommandozeile läuft durch, aber nur im IDE ist alles rot | IDE-Cache. Visual Studio neu starten, die Solution schließen und obj/ sowie bin/ löschen |
| Änderungen am Generator wirken sich nicht aus | Dasselbe. Die DLL des Generators kann im Prozess des IDE geladen bleiben |
| Es erscheint ein Fehler über doppelte Typen oder Elemente | Das Generierungsziel ist Teil der Kompilierung. Mit dem oben genannten <Compile Remove> ausschließen |
| Es erscheint kein Fehler, aber es wird auch nichts generiert | Möglicherweise wird im Generator eine Ausnahme geworfen. Diese Ausnahme tritt manchmal nicht klar sichtbar zutage |
Zur Eingrenzung ist diese Reihenfolge schnell.
1. Mit dotnet build auf der Kommandozeile prüfen, ob dasselbe Symptom auftritt
→ Tritt es nicht auf, liegt das Problem beim IDE-Cache
2. EmitCompilerGeneratedFiles aktivieren und prüfen, ob eine .g.cs-Datei erscheint
→ Erscheint keine, wird der Generator nicht aufgerufen (Referenzierungsart prüfen)
→ Erscheint eine, den Inhalt lesen (Problem im Generierungsinhalt)
3. Am Einstiegspunkt des Generators ein Diagnostic melden und prüfen, ob dieser Punkt erreicht wird
4. Bleibt die Ursache unklar, einen Test schreiben
Schluckt der Generator Ausnahmen nicht, sondern fängt sie mit try/catch ab und meldet sie selbst über ReportDiagnostic, tritt das letzte Symptom der Tabelle (nichts erscheint, nichts wird generiert) deutlich seltener auf.
„Den Debugger anhängen“ ist das letzte Mittel. In den meisten Fällen ist die Eingrenzung über die Schritte 1 bis 3 schneller, und da ein Generator sowohl vom IDE als auch vom Build aufgerufen wird, kostet eine Verwechslung, welchen der beiden Prozesse man gerade betrachtet, viel Zeit.
31. Tests mit Roslyn
Analyzer und Source Generatoren sollten durch Tests entwickelt und gepflegt werden. Besonders beim Analyzer sind sowohl Fehlalarme als auch übersehene Fälle ein Problem.
Für Tests sollte man Muster wie diese vorbereiten.
Code, der erkannt werden soll
Code, der nicht erkannt werden darf
Code mit using-Alias
Code mit vollständig qualifiziertem Namen
Code mit einem ähnlich benannten, aber anderen Typ
Als generierter Code eingestufter Code
Code bei aktiviertem Nullable
Verbietet man zum Beispiel System.DateTime.Now, sollte man diese Fälle prüfen.
// sollte erkannt werden
var x = System.DateTime.Now;
// sollte auch bei vorhandenem using erkannt werden
using System;
var x = DateTime.Now;
// bei einem anderen Typ darf nichts erkannt werden
namespace MyCompany;
public static class DateTime
{
public static string Now => "now";
}
var x = DateTime.Now;
Der letzte Fall ist ein Beispiel, bei dem man sich mit Textsuche leicht irrt. Bei einem Roslyn-Analyzer lässt sich das vermeiden, indem man mit dem SemanticModel das Zielsymbol prüft.
32. Kann man Roslyn auch in .NET-Framework-Projekten verwenden?
Roslyn ist nicht nur für das aktuelle .NET gedacht. Je nach „wie man es einsetzt“ ändern sich jedoch die Vorsichtspunkte.
Einsatz als Untersuchungswerkzeug
Es ist eine realistische Option, ein Roslyn-Werkzeug als .NET-8- oder .NET-10-Konsolenanwendung zu bauen und damit eine .NET-Framework-Solution einzulesen und zu analysieren.
In diesem Fall läuft das Werkzeug selbst auf dem aktuellen .NET, während der Analysegegenstand .NET-Framework-Code sein kann.
Beim Einlesen einer Solution über MSBuildWorkspace benötigt man jedoch eine Umgebung mit MSBuild, SDK, Referenzassemblies und NuGet-Wiederherstellung, mit der sich das Zielprojekt tatsächlich bauen lässt.
Das heißt, Roslyn kann nicht einfach alles allein lesen — um die tatsächliche Projektstruktur aufzulösen, ist eine Build-Umgebung erforderlich.
Einsatz als Analyzer
Ein Analyzer wird vom Compiler oder IDE geladen und ausgeführt.
Auch wenn das Zielprojekt .NET Framework ist, lässt er sich verwenden, sofern der Compiler den Analyzer laden kann.
Bei alten csproj-Dateien, alten Visual-Studio-Versionen, altem MSBuild oder einer auf packages.config basierenden Konfiguration ist die Einführung und der Betrieb allerdings mitunter nicht so unkompliziert wie im aktuellen SDK-Stil.
Bei der Einführung in ein bestehendes .NET-Framework-Projekt sollte man zunächst Folgendes prüfen.
Version von Visual Studio / MSBuild
Lässt sich PackageReference verwenden?
Läuft derselbe Analyzer auch in der CI?
Erscheinen Warnungen im Build-Protokoll?
Wirkt sich .editorconfig aus?
Einsatz als Source Generator
Ein Source Generator wird zur Kompilierzeit vom Compiler geladen.
Deshalb ist nicht so sehr das Laufzeit-Framework des Zielprojekts entscheidend, sondern welchen Compiler und welches SDK der Build verwendet.
Bei aktuellen SDK-Stil-Projekten des heutigen .NET ist das gut handhabbar, bei alten .NET-Framework-Projekten kann je nach Projektformat und Build-Umgebung Vorsicht nötig sein.
Für bestehende .NET-Framework-Bestände ist es oft sicherer, nicht sofort einen Source Generator einzubauen, sondern zunächst mit einem Roslyn-basierten Untersuchungswerkzeug oder einem Analyzer zu beginnen.
33. Hinweise zur Versionswahl
Zu den Roslyn-bezogenen NuGet-Paketen gehören Microsoft.CodeAnalysis.*.
Typisch sind unter anderem diese.
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.CSharp.Workspaces
Microsoft.CodeAnalysis.Workspaces.MSBuild
Microsoft.CodeAnalysis.Analyzers
Microsoft.CodeAnalysis.CSharp.CodeFix.Testing
Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing
Wichtig ist hier, dass Analyzer und Source Generatoren im Compiler auf Seiten des Nutzers geladen werden.
Das heißt: Ist das SDK/Visual Studio auf dem Rechner des Entwicklers oder in der CI veraltet, funktioniert ein Analyzer/Generator, der eine zu neue Roslyn-API verwendet, mitunter nicht.
Lässt sich die Build-Umgebung intern vereinheitlichen, kann man vergleichsweise neue APIs gut nutzen.
Bei extern verteilten Bibliotheken muss man dagegen im Hinblick auf ein breites Nutzerumfeld die Version von Microsoft.CodeAnalysis, von der man abhängt, sorgfältig wählen.
Als Leitlinie kann man so denken.
Nur intern: CI und Entwicklungsumgebung angleichen und dann neuere APIs verwenden
Externe Verteilung: konservativ wählen, mit Rücksicht auf das SDK-/VS-Spektrum der Nutzer
Generator: nach Möglichkeit als Incremental Generator entwerfen
Analyzer: Leichtgewichtigkeit priorisieren, um die IDE-Erfahrung nicht zu beeinträchtigen
Roslyn liegt nahe am Compiler, daher wirken sich Versionsunterschiede stark aus.
34. Nicht versuchen, mit Roslyn alles zu lösen
Roslyn ist mächtig, aber kein Werkzeug, das alle Probleme löst. Diese Probleme lassen sich zum Beispiel nicht allein mit Roslyn lösen.
Welcher Zweig zur Laufzeit durchlaufen wird
Welcher Wert bei Produktionsdaten ankommt
Welche Methode per Reflection dynamisch aufgerufen wird
Das Ergebnis der Laufzeitregistrierung eines DI-Containers
Verarbeitung, die sich je nach Konfigurationsdatei ändert
Ein Wert, der von einem externen Dienst zurückkommt
Roslyn ist in erster Linie ein Werkzeug für Quellcode und Kompilierinformationen. Möchte man das Laufzeitverhalten kennen, braucht man andere Mittel wie Tests, Logs, Traces, Profiling oder Dump-Analyse.
Die Rolle von Roslyn sollte man daher so verstehen.
Das statisch Erkennbare mit hoher Präzision behandeln
Versucht man, auch dynamisch nur Erkennbares mit Roslyn zwanghaft zu lösen, entsteht ein komplexer und ungenauer Mechanismus.
35. Reihenfolge der Einführung
Um Roslyn praktisch einzuführen, empfiehlt sich diese Reihenfolge.
1. Vorhandene .NET-Analyzer und .editorconfig einrichten
2. Ein kleines Untersuchungswerkzeug mit Syntax Tree schreiben
3. Mit dem SemanticModel Typauflösung ausprobieren
4. Mit MSBuildWorkspace eine Solution einlesen
5. Einen kleinen teamspezifischen Analyzer bauen
6. Bei Bedarf einen Code Fix hinzufügen
7. Für Stellen mit viel Standardcode einen Source Generator in Erwägung ziehen
Man muss nicht von Anfang an zu Source Generatoren gehen. In vielen Projekten zeigen zuerst Analyzer und Untersuchungswerkzeuge Wirkung.
Besonders bei großem Bestandscode ist dieser Ablauf realistisch.
Mit einem Untersuchungswerkzeug den Ist-Zustand erfassen
Häufige Probleme als Analyzer umsetzen
Nur sicher korrigierbare Fälle als Code Fix umsetzen
Häufig wiederholten Standardcode als Generator umsetzen
Roslyn ist ein Werkzeug, das sich schrittweise einsetzen lässt.
36. Ein kleines Beispiel: Methodenaufrufe auflisten
Zum Schluss betrachten wir die Verwendung von Roslyn noch etwas praxisnäher. Hier geht es um die Vorstellung, Methodenaufrufe innerhalb einer Solution aufzulisten.
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
MSBuildLocator.RegisterDefaults();
using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(args[0]);
foreach (var project in solution.Projects)
{
var compilation = await project.GetCompilationAsync();
if (compilation is null)
{
continue;
}
foreach (var document in project.Documents)
{
var tree = await document.GetSyntaxTreeAsync();
if (tree is null)
{
continue;
}
var root = await tree.GetRootAsync();
var semanticModel = compilation.GetSemanticModel(tree);
var invocations = root
.DescendantNodes()
.OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
var symbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;
if (symbol is null)
{
continue;
}
var lineSpan = invocation.GetLocation().GetLineSpan();
var line = lineSpan.StartLinePosition.Line + 1;
Console.WriteLine(string.Join(",", new[]
{
project.Name,
document.FilePath ?? document.Name,
line.ToString(),
symbol.ContainingType.ToDisplayString(),
symbol.Name
}));
}
}
}
Erweitert man ein solches Werkzeug etwas, lassen sich damit Untersuchungen wie diese durchführen.
Nur Aufrufe einer bestimmten Methode extrahieren
Fundstellen veralteter APIs ausgeben
Die Nutzungshäufigkeit pro Projekt ausgeben
Eine Liste der Migrationsziel-APIs erstellen
Wenn man Quellcode aus der Sicht des Compilers lesen kann, wird die Untersuchung von Bestandscode deutlich leichter.
37. Hinweise beim Umschreiben von Code mit Roslyn
Mit Roslyn lässt sich Code auch über den Syntaxbaum umschreiben.
So kann man zum Beispiel bestimmte Methodennamen ändern, Attribute hinzufügen oder usings hinzufügen.
Codeumschreibung sollte allerdings mit Vorsicht erfolgen. Zu beachten sind unter anderem diese Punkte.
Sicherstellen, dass sich die Bedeutung nicht ändert
Kommentare und Leerraum nicht zerstören
Diffs nicht zu groß werden lassen
Die Formatierung vereinheitlichen
Nicht zu viele Umwandlungen auf einmal durchführen
Git-Diffs leicht überprüfbar halten
Da der Syntax Tree von Roslyn Trivia beibehält, ist eine Umwandlung unter Erhalt von Kommentaren und Leerraum möglich. Erzeugt man Knoten jedoch nachlässig, kann die Formatierung des generierten Codes durcheinandergeraten.
Beim Bau eines Umschreibewerkzeugs ist dieser Ansatz sicher.
Zunächst nur die Erkennung durchführen
Den Diff vor und nach der Umwandlung prüfen
Mit kleinen Umwandlungen beginnen
Für das Umwandlungswerkzeug selbst Tests schreiben
In der CI zunächst im Erkennungsmodus arbeiten
Bei großangelegten maschinellen Umwandlungen ist Roslyn mächtig, aber am Ende ist ein menschliches Review nötig.
38. Roslyn und KI-gestützte Codierhilfen
In den letzten Jahren sind auch KI-gestützte Codegenerierung und Review-Unterstützung üblich geworden, aber auch dabei sinkt der Wert von Roslyn nicht. KI ist stark im Umgang mit natürlicher Sprache und Umgebungskontext, Roslyn ist stark darin, als Compiler präzise Syntax- und Bedeutungsinformationen zu behandeln. Beide stehen eher in einem ergänzenden als einem konkurrierenden Verhältnis.
Man kann sich zum Beispiel diese Arbeitsteilung vorstellen.
Mit Roslyn die Zielstelle präzise extrahieren
Mit KI Korrekturrichtung und Erklärungstext generieren
Mit Roslyn prüfen, ob der Korrekturvorschlag kompilierbar ist
Mit dem Analyzer ein erneutes Auftreten verhindern
Statt die KI zu bitten, „alle veralteten APIs in dieser Codebasis zu korrigieren“, ist es mitunter sicherer, die Zielstellen mit Roslyn präzise zu extrahieren.
Für die Überlegung der Korrekturrichtung und als Review-Unterstützung ist der Einsatz von KI dann praxisnäher.
Was der Compiler versteht, wird dem Compiler überlassen. Mensch und KI konzentrieren sich auf die Beurteilung darüber hinaus.
Diese Aufteilung ist wichtig.
39. Praxis-Checkliste
Bevor man Roslyn einsetzt, sollte man Folgendes prüfen.
Geht es um Untersuchung, Warnung, Korrektur oder Generierung?
Genügt Syntax, oder ist semantische Analyse nötig?
Genügt eine einzelne Datei, oder ist das ganze Projekt nötig?
Muss es im IDE laufen, oder genügt ein Einmalwerkzeug?
Darf sich das auf die Buildzeit auswirken?
Wird es in der CI ausgeführt?
Entstehen keine massenhaften Warnungen im Bestandscode?
Wie soll der Schweregrad des Analyzers gewählt werden?
Lässt sich der Code Fix sicher anwenden?
Lässt sich der vom Source Generator erzeugte Code einsehen?
Sind SDK-/Visual-Studio-Version der Nutzer aufeinander abgestimmt?
Bei der Entscheidung hilft diese Einteilung.
Untersuchen wollen -> Roslyn-basiertes Konsolenwerkzeug
Immer einhalten lassen -> Analyzer
Korrektur ist bereits klar -> Code Fix
Standardcode erzeugen wollen -> Source Generator
Mit dieser Einteilung lässt sich der Einsatz von Roslyn seltener verfehlen.
40. Zusammenfassung
Roslyn öffnet den Compiler von C# und Visual Basic als eine für Entwickler nutzbare API.
Mit Roslyn lässt sich Quellcode nicht als bloße Zeichenkette, sondern auf diese Weisen behandeln.
Als Syntax Tree die Syntax lesen
Als SemanticModel die Bedeutung lesen
Als Compilation die gesamte Kompilierung behandeln
Als Workspace Solution und Projekt behandeln
Als Analyzer Warnungen ausgeben
Als Code Fix Korrekturvorschläge anbieten
Als Source Generator Code generieren
In der Praxis ist das besonders in diesen Situationen nützlich.
Untersuchung bestehender Codebasen
Migrationsunterstützung von .NET Framework zu .NET
Automatische Prüfung von Team-Konventionen
Anleitung für Nutzer einer Bibliothek
Generierung von Standardcode
Qualitätssicherung im IDE und in der CI
Wichtig ist, Roslyn nicht zu sehr als „schwierige Compilertechnik“ zu betrachten.
Zu Beginn genügt es, eine einzelne Datei mit CSharpSyntaxTree.ParseText zu lesen und Methodennamen aufzulisten. Von dort aus lässt sich der Umfang schrittweise auf SemanticModel, Workspace, Analyzer und Source Generator ausweiten.
Roslyn lässt sich mit einem Satz zusammenfassen.
C#-Code nicht als Zeichenkette, sondern als vom Compiler verstandene Struktur behandeln können.
Mit dieser Sichtweise lassen sich Code-Review, Migration, Untersuchung und Generierungsautomatisierung deutlich leichter angehen.
Quellen
- Das gesamte Beispielcode-Paket zu diesem Artikel (Bibliothek, Analyzer/Source Generator, Demo, Unit-Tests) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/roslyn-dotnet-compiler-platform
- dotnet/roslyn - GitHub
- The .NET Compiler Platform SDK - Microsoft Learn
- Get started with syntax analysis - Microsoft Learn — zur Installation des .NET Compiler Platform SDK und zur Nutzung des Syntax Visualizer
- Tutorial: Write your first analyzer and code fix - Microsoft Learn — Vorgehen, ausgehend von der Vorlage „Analyzer with Code Fix (.NET Standard)“
- SharpLab — Browserbasierter Playground für C#/VB/F#
- Work with syntax - Microsoft Learn
- Work with semantics - Microsoft Learn
- Work with a workspace - Microsoft Learn
- Overview of .NET source code analysis - Microsoft Learn
- Code analysis using .NET compiler platform analyzers - Microsoft Learn
- Get started with Roslyn analyzers - Microsoft Learn
- Introducing C# Source Generators - .NET Blog
- Source Generator Cookbook - GitHub
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Praktische Best Practices für Multithreading: .NET-Edition — Was Sie entscheiden sollten, bevor Sie weitere Threads hinzufügen
Eine praxisnahe Übersicht der Entwurfsregeln, die verhindern, dass Multithreading-Code in .NET/C# gelegentlich abstürzt oder hängen bleib...
WMI/CIM aus C# und PowerShell verwenden — Praxisleitfaden für Hardwareinformationen, Prozessüberwachung und Remoteabfragen
WMI/CIM ist die Standardlösung, um die Seriennummer eines PCs auszulesen, freien Festplattenspeicher zu überwachen und den Start von Proz...
Wie lange lässt sich MSMQ noch nutzen? ── Die Migrationsentscheidung für eine Legacy-Queue, die nicht einmal „deprecated“ ist
MSMQ steht nicht auf Microsofts offiziellen Deprecated-Listen, doch System.Messaging existiert nur im .NET Framework und blockiert die Mi...
Code-Design für Geschäftssysteme — Produkt- und Kundencodes festlegen, und Prüfziffern
Ein praxisnaher Leitfaden zur Festlegung des Codesystems eines Geschäftssystems, einschließlich Produkt- und Kundencodes. Behandelt eine ...
Abwärtskompatibilität von DLL- und COM-Schnittstellen — Eine Entscheidungstabelle dafür, welche Änderungen Aufrufer brechen
Welche Änderungen an einer DLL oder COM-Komponente brechen tatsächlich deren Aufrufer? Wir legen die drei Ebenen der Kompatibilität dar —...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Was ist Roslyn?
- Roslyn ist, offiziell als .NET Compiler Platform bezeichnet, eine Compiler-Implementierung für C# und Visual Basic und zugleich eine Sammlung von APIs zum Bau von Code-Analysewerkzeugen. Was früher eine schwarze Box war, macht die Informationen, die der Compiler intern erzeugt (welchem Typ dieser Bezeichner entspricht, welche Methode dieser Aufruf meint usw.), für Anwendungen und Werkzeuge nutzbar. Dadurch lässt sich C#-Code nicht als Zeichenkette, sondern als Syntax lesen, nicht nach Aussehen, sondern nach Bedeutung interpretieren, und man kann Warnungen, Korrekturvorschläge und generierten Code ausgeben.
- Was kann man mit Roslyn machen?
- Man kann C#/VB syntaktisch analysieren, Typen und Methoden semantisch auflösen, ganze Projekte oder Solutions analysieren, eigene Analyzer, Code Fixes und Source Generatoren schreiben sowie Code generieren und transformieren. Praxisnah ausgedrückt: verbotene APIs als Build-Warnung markieren, Fundstellen veralteter APIs auflisten, DTO-Mapping-Code zur Kompilierzeit erzeugen oder Migrationsuntersuchungen von .NET Framework nach .NET unterstützen. Die Einsatzformen lassen sich grob in vier Kategorien einteilen: Verwendung als Bibliothek, Analyzer, Code Fix und Source Generator.
- Was unterscheidet Roslyn von der Codesuche mit regulären Ausdrücken oder grep?
- Mit regulären Ausdrücken ist es schwierig, Zeichenketten in Kommentaren, Zeichenkettenliterale, über Zeilenumbrüche verteilte Aufrufe oder Aufrufe über using-Aliase korrekt zu behandeln. Mit Roslyn lassen sich Kommentare, Zeichenkettenliterale, syntaktische Methodenaufrufe und tatsächlich aufgelöste Methoden sauber unterscheiden. Für eine grobe Suche reicht eine Textsuche oft aus; sollen jedoch Designentscheidungen oder automatische Korrekturen auf den Ergebnissen aufbauen, ist Roslyn sicherer, weil es auf dem Namensauflösungsergebnis des Compilers basiert.
- Wo sollte man mit dem Lernen von Roslyn beginnen?
- Es ist nicht nötig, gleich mit Source Generatoren zu beginnen. Empfehlenswert ist, zunächst die im .NET SDK enthaltenen Analyzer und die .editorconfig einzurichten, dann ein kleines Untersuchungswerkzeug zu schreiben, das eine einzelne Datei mit CSharpSyntaxTree.ParseText liest und Methodennamen auflistet, und von dort aus zur Typauflösung mit dem SemanticModel, zum Einlesen von Solutions mit MSBuildWorkspace und schließlich zu kleinen teamspezifischen Analyzern überzugehen. In vielen Projekten zeigen Analyzer und Untersuchungswerkzeuge schneller Wirkung als Source Generatoren.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.