Wird geladen...

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:


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?


Leistung und bewährte Praktiken


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