Codeanalyse mit der Roslyn Compiler API in C#
Lernen Sie Codeanalyse in C# mit der Roslyn Compiler API, einschließlich Syntaxbäumen und Codegenerierung.
Auf der .NET-Plattform stellt Roslyn (Microsoft.CodeAnalysis) die C#- und VB-Compiler vollständig in verwaltetem Code bereit.
Die Roslyn Compiler API ermöglicht es, Quellcode programmgesteuert zu lesen, zu analysieren, zu ändern und sogar neu zu kompilieren.
Dadurch können statische Analysetools, automatische Codekorrekturen (Code Fixes), Linters oder benutzerdefinierte IDE-Erweiterungen entwickelt werden.
Was ist die Roslyn Compiler API?
Roslyn ist der C#-Compiler, der Entwicklern als API zur Verfügung gestellt wird.
Sie ermöglicht es, alle Schritte, die der Visual-Studio- oder dotnet-Compiler normalerweise durchführt (Parsen, Analysieren, Kompilieren), direkt im eigenen Code auszuführen.
Sie besteht aus den folgenden Hauptkomponenten:
- Syntax API: Ermöglicht den Zugriff auf die syntaktische Struktur des Quellcodes in Baumform.
- Semantic API: Liefert semantische Informationen wie Variablentypen, Symbole und den Kompilierungskontext.
- Compilation API: Führt die Kompilierung durch, analysiert Warnungen/Fehler und erzeugt IL-Ausgabe (Intermediate Language).
Installation
Um die Roslyn API zu verwenden, fügen Sie das NuGet-Paket Microsoft.CodeAnalysis.CSharp hinzu:
dotnet add package Microsoft.CodeAnalysis.CSharp
Erstellen eines Syntaxbaums aus Quellcode
Der erste Schritt besteht darin, C#-Code in einen Syntaxbaum zu konvertieren. Dieser Baum stellt die strukturellen Elemente des Codes als Knoten dar.
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
class Program
{
static void Main()
{
string code = @"
using System;
class Hallo
{
static void Main()
{
Console.WriteLine(""Hallo Roslyn!"");
}
}";
SyntaxTree baum = CSharpSyntaxTree.ParseText(code);
var wurzel = baum.GetRoot();
Console.WriteLine($"Wurzelknoten: {wurzel.Kind()}");
foreach (var node in wurzel.DescendantNodes())
Console.WriteLine(node.Kind());
}
}
Die Ausgabe listet Knotentypen wie ClassDeclaration, MethodDeclaration und InvocationExpression auf.
Semantisches Modell: Bedeutung des Codes analysieren
Das SemanticModel liefert detaillierte Informationen zu Variablentypen, Methodensignaturen und Symbolen. So lässt sich z. B. ermitteln, zu welcher Klasse ein Methodenaufruf gehört oder welchen Typ eine Variable hat.
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
class Program
{
static void Main()
{
string code = @"
using System;
class Test
{
static void Main()
{
string nachricht = ""Hallo!"";
Console.WriteLine(nachricht);
}
}";
SyntaxTree baum = CSharpSyntaxTree.ParseText(code);
var kompilation = CSharpCompilation.Create("TestKompilation")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(baum);
var modell = kompilation.GetSemanticModel(baum);
var wurzel = baum.GetRoot();
var variable = wurzel.DescendantNodes().OfType<VariableDeclaratorSyntax>().First();
var symbol = modell.GetDeclaredSymbol(variable);
Console.WriteLine($"Variablenname: {symbol.Name}, Typ: {symbol.GetSymbolType()?.Name}");
}
}
In diesem Beispiel wird der Typ (string) der Variablen nachricht ermittelt.
Roslyn entnimmt diese Information der Symboltabelle – es handelt sich nicht nur um eine Textsuche.
Syntax Rewriter: Code verändern
Mit Roslyn kann man nicht nur Code analysieren, sondern auch Code transformieren (Refactoring).
Mit der Klasse CSharpSyntaxRewriter können bestimmte Knoten verändert oder entfernt werden.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
class UppercaseRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node)
{
if (node.IsKind(SyntaxKind.StringLiteralExpression))
{
string neu = node.Token.ValueText.ToUpperInvariant();
return SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal(neu));
}
return base.VisitLiteralExpression(node);
}
}
class Program
{
static void Main()
{
string code = @"class A { void M() { System.Console.WriteLine(""hallo""); } }";
SyntaxTree baum = CSharpSyntaxTree.ParseText(code);
var rewriter = new UppercaseRewriter();
var neueWurzel = rewriter.Visit(baum.GetRoot());
Console.WriteLine(neueWurzel.ToFullString());
}
}
Dieses Beispiel wandelt alle String-Literale in Großbuchstaben um.
Somit wird "hallo" zu "HALLO".
Diagnostik: Fehler- und Warnungsanalyse
Das Compilation-Objekt kann Fehler und Warnungen zurückgeben, die während der Kompilierung auftreten.
So lassen sich eigene Regeln definieren und statische Analysetools erstellen.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Linq;
class Program
{
static void Main()
{
var code = "class A { void M() { int x = \"falsch\"; } }";
var baum = CSharpSyntaxTree.ParseText(code);
var kompilation = CSharpCompilation.Create("Analyse")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(baum);
var fehler = kompilation.GetDiagnostics();
foreach (var f in fehler)
Console.WriteLine($"{f.Id}: {f.GetMessage()}");
}
}
Ausgabe: CS0029: Der Typ 'string' kann nicht implizit in 'int' konvertiert werden
Auf diese Weise können Compilerfehler oder eigene Analyse-Regeln erkannt werden.
Beispiel: „Nicht verwendete Variable“-Analyse
Im folgenden Beispiel wird die Roslyn-API verwendet, um Variablen zu erkennen, die deklariert, aber nie verwendet wurden. Dies funktioniert ähnlich wie die grüne „nicht verwendet“-Warnung in Visual Studio.
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
class Program
{
static void Main()
{
string code = @"
class Test
{
void Methode()
{
int x = 10;
int y = 20;
Console.WriteLine(x);
}
}";
var baum = CSharpSyntaxTree.ParseText(code);
var kompilation = CSharpCompilation.Create("Analyse")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(baum);
var modell = kompilation.GetSemanticModel(baum);
var wurzel = baum.GetRoot();
var variablen = wurzel.DescendantNodes().OfType<VariableDeclaratorSyntax>();
foreach (var v in variablen)
{
var symbol = modell.GetDeclaredSymbol(v);
var verwendungen = wurzel.DescendantNodes()
.OfType<IdentifierNameSyntax>()
.Count(i => i.Identifier.Text == v.Identifier.Text);
if (verwendungen <= 1)
Console.WriteLine($"Warnung: Variable '{v.Identifier.Text}' wird nicht verwendet.");
}
}
}
Ausgabe: Warnung: Variable 'y' wird nicht verwendet.
Dieses einfache Beispiel zeigt die Funktionsweise statischer Code-Analysetools.
Was kann man mit Roslyn entwickeln?
- Statische Code-Analysetools (z. B. Stil-, Sicherheits- oder Leistungsregeln)
- Code Analyzer & Code Fix Provider (Visual-Studio-Erweiterungen)
- Source Generators (Codegenerierung zur Kompilierzeit)
- Refactoring- oder automatische Formatierungstools
- Code-Transformation und -Umschreibung (z. B. Ersetzen veralteter APIs)
Leistung und bewährte Praktiken
- Roslyn-Analysen sind CPU- und speicherintensiv; bei großen Projekten sollte inkrementelle Analyse bevorzugt werden.
SyntaxTree-Objekte sind unveränderlich; verwenden SieWith...-Methoden oder einen Rewriter, um Änderungen vorzunehmen.- Vermeiden Sie in großen Codebasen unnötige Baumdurchläufe, indem Sie gefilterte Abfragen (
OfType<T>) verwenden. - Wenn Sie IDE-Erweiterungen entwickeln, führen Sie Analysen asynchron im Hintergrund aus.
TL;DR
- Die Roslyn Compiler API bietet eine programmatische Schnittstelle zum Analysieren, Transformieren und Kompilieren von C#-Code.
- Syntax API liefert die syntaktische Struktur (Syntaxbaum), während die Semantic API Bedeutungsinformationen (Typ/Symbol) bereitstellt.
- Mit dieser API können benutzerdefinierte Code-Analyzer oder Refactoring-Tools entwickelt werden.
- Im Gegensatz zu Reflection konzentriert sich Roslyn auf die Analyse zur Kompilierzeit.
- In großen Projekten sind inkrementelle Analyse und Leistungsoptimierung entscheidend.
Ähnliche Artikel
Das Konzept von Source Generators in C# (C# 9+)
Lernen Sie Source Generators in C#, um Code zur Compile-Zeit zu erzeugen und Performance zu verbessern.
Reflection und Late Binding in C#
Lernen Sie Reflection und Late Binding in C#, um Typen zur Laufzeit zu analysieren und dynamische Systeme zu erstellen.
SOLID-Prinzipien mit C#
SOLID-Prinzipien mit C#-Beispielen: flexiblen, wartbaren und testbaren Code erstellen.