Delen via


Aan de slag met syntaxisanalyse

In deze zelfstudie verkent u de Syntaxis-API. De Syntaxis-API biedt toegang tot de gegevensstructuren die een C#- of Visual Basic-programma beschrijven. Deze gegevensstructuren hebben voldoende details om alle programma's van elke grootte volledig te kunnen vertegenwoordigen. Deze structuren kunnen volledige programma's beschrijven die correct worden gecompileerd en uitgevoerd. Ze kunnen ook onvolledige programma's beschrijven, terwijl u ze schrijft, in de editor.

Als u deze uitgebreide expressie wilt inschakelen, zijn de gegevensstructuren en API's waaruit de Syntaxis-API bestaat, noodzakelijkerwijs complex. Laten we beginnen met hoe de gegevensstructuur eruitziet voor het typische Hallo wereld-programma:

using System;
using System.Collections.Generic;
using System.Linq;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Bekijk de tekst van het vorige programma. U herkent vertrouwde elementen. De hele tekst vertegenwoordigt één bronbestand of een compilatie-eenheid. Voor de eerste drie regels van dat bronbestand worden instructies gebruikt. De resterende bron is opgenomen in een naamruimtedeclaratie. De declaratie van de naamruimte bevat een onderliggende klassedeclaratie. De klassedeclaratie bevat één methodedeclaratie.

De syntaxis-API maakt een structuur met de hoofdmap die de compilatie-eenheid vertegenwoordigt. Knooppunten in de structuur vertegenwoordigen de using-instructies, naamruimtedeclaratie en alle andere elementen van het programma. De structuur van de boomstructuur loopt door tot de laagste niveaus: de tekenreeks 'Hallo wereld!' is een letterlijk tekenreekstoken dat afstamt van een argument. De Syntaxis-API biedt toegang tot de structuur van het programma. U kunt query's uitvoeren op specifieke codeprocedures, de hele structuur doorlopen om de code te begrijpen en nieuwe structuren maken door de bestaande structuur te wijzigen.

Deze korte beschrijving biedt een overzicht van het soort informatie dat toegankelijk is met behulp van de syntaxis-API. De syntaxis-API is niets meer dan een formele API die de bekende codeconstructies beschrijft die u kent van C#. De volledige mogelijkheden omvatten informatie over de indeling van de code, waaronder regeleinden, witruimte en inspringing. Met behulp van deze informatie kunt u de code volledig weergeven als geschreven en gelezen door menselijke programmeurs of de compiler. Als u deze structuur gebruikt, kunt u op een zeer betekenisvol niveau communiceren met de broncode. Het zijn geen tekenreeksen meer, maar gegevens die de structuur van een C#-programma vertegenwoordigen.

Om aan de slag te gaan, moet u de .NET Compiler Platform SDK installeren:

Installatie-instructies - Visual Studio Installer

Er zijn twee verschillende manieren om de .NET Compiler Platform SDK te vinden in het Visual Studio-installatieprogramma:

Installeren met behulp van het visual studio-installatieprogramma - weergave Workloads

De .NET Compiler Platform SDK wordt niet automatisch geselecteerd als onderdeel van de ontwikkelworkload van de Visual Studio-extensie. U moet het selecteren als een optioneel onderdeel.

  1. Visual Studio Installer uitvoeren
  2. Selecteer Wijzigen
  3. Controleer de workload Voor het ontwikkelen van de Visual Studio-extensie .
  4. Open het ontwikkelknooppunt visual Studio-extensie in de overzichtsstructuur.
  5. Schakel het selectievakje in voor .NET Compiler Platform SDK. U vindt deze als laatste onder de optionele onderdelen.

Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in het visualizer:

  1. Open het knooppunt Afzonderlijke onderdelen in de overzichtsstructuur.
  2. Schakel het selectievakje in voor DGML-editor

Installeren met behulp van het visual studio-installatieprogramma - tabblad Afzonderlijke onderdelen

  1. Visual Studio Installer uitvoeren
  2. Selecteer Wijzigen
  3. Selecteer het tabblad Afzonderlijke onderdelen
  4. Schakel het selectievakje in voor .NET Compiler Platform SDK. U vindt deze bovenaan in de sectie Compilers, buildhulpprogramma's en runtimes .

Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in het visualizer:

  1. Schakel het selectievakje voor DGML-editor in. U vindt deze in de sectie Codehulpprogramma's .

Syntaxisstructuren begrijpen

U gebruikt de syntaxis-API voor elke analyse van de structuur van C#-code. De Syntaxis-API maakt de parsers, de syntaxisstructuren en hulpprogramma's beschikbaar voor het analyseren en maken van syntaxisstructuren. Dit is hoe u code zoekt naar specifieke syntaxiselementen of hoe u de code voor een programma leest.

Een syntaxisstructuur is een gegevensstructuur die door de C#- en Visual Basic-compilers wordt gebruikt om inzicht te verkrijgen in C# en Visual Basic-programma's. Syntaxisstructuren worden geproduceerd door dezelfde parser die wordt uitgevoerd wanneer een project wordt gebouwd of een ontwikkelaar F5 bereikt. De syntaxisstructuren zijn volledig trouw aan de taal; elk stukje informatie in een codebestand wordt weergegeven in de structuur. Als u een syntaxisstructuur naar tekst schrijft, wordt de exacte oorspronkelijke tekst gereproduceerd die is geparseerd. De syntaxisstructuren zijn ook onveranderbaar; zodra een syntaxisstructuur is gemaakt, kan deze nooit worden gewijzigd. Gebruikers van de structuren kunnen de structuren op meerdere threads analyseren, zonder vergrendelingen of andere gelijktijdigheidsmaatregelen, in de wetenschap dat de gegevens nooit worden gewijzigd. U kunt API's gebruiken om nieuwe structuren te maken die het resultaat zijn van het wijzigen van een bestaande structuur.

De vier primaire bouwstenen van syntaxisstructuren zijn:

Trivia, tokens en knooppunten zijn hiërarchisch samengesteld om een structuur te vormen die alles volledig vertegenwoordigt in een fragment van Visual Basic- of C#-code. U kunt deze structuur zien met behulp van het venster Syntaxis visualiseren . Kies in Visual Studio View>Other Windows>Syntax Visualizer. Het voorgaande C#-bronbestand dat met syntaxis visualiseren is onderzocht, ziet er bijvoorbeeld uit zoals in de volgende afbeelding:

SyntaxNode: Blauw | SyntaxToken: Groen | SyntaxTrivia: Red C#-codebestand

Als u door deze structuurstructuur navigeert, kunt u elke instructie, expressie, token of beetje witruimte in een codebestand vinden.

Hoewel u alles in een codebestand kunt vinden met behulp van de syntaxis-API's, omvatten de meeste scenario's het onderzoeken van kleine codefragmenten of het zoeken naar bepaalde instructies of fragmenten. In de twee volgende voorbeelden ziet u een typisch gebruik om door de structuur van code te bladeren of te zoeken naar enkele instructies.

Doorkruising van bomen

U kunt de knooppunten in een syntaxisstructuur op twee manieren onderzoeken. U kunt de structuur doorkruisen om elk knooppunt te onderzoeken, of u kunt een query uitvoeren op specifieke elementen of knooppunten.

Handmatig doorkruisen

U kunt de voltooide code voor dit voorbeeld bekijken in onze GitHub-opslagplaats.

Notitie

De syntaxisstructuurtypen gebruiken overname om de verschillende syntaxiselementen te beschrijven die geldig zijn op verschillende locaties in het programma. Het gebruik van deze API's betekent vaak het casten van eigenschappen of verzamelingsleden naar specifieke afgeleide typen. In de volgende voorbeelden zijn de toewijzing en de casts afzonderlijke instructies, waarbij expliciet getypte variabelen worden gebruikt. U kunt de code lezen om de retourtypen van de API en het runtimetype van de geretourneerde objecten te zien. In de praktijk is het gebruikelijker om impliciet getypte variabelen te gebruiken en te vertrouwen op API-namen om het type objecten te beschrijven dat wordt onderzocht.

Maak een nieuw C# Stand-Alone Code Analysis Tool-project :

  • Kies in Visual Studio Bestand>Nieuw>project om het dialoogvenster Nieuw project weer te geven.
  • Kies onder Visual C#>Extensibilityde optie Zelfstandig hulpprogramma voor codeanalyse.
  • Geef uw project de naam SyntaxTreeManualTraversal en klik op OK.

U gaat het eenvoudige programma 'Hallo wereld!' analyseren dat eerder is weergegeven. Voeg de tekst voor het Hallo wereld-programma toe als een constante in uw Program klasse:

        const string programText =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Hello, World!"");
        }
    }
}";

Voeg vervolgens de volgende code toe om de syntaxisstructuur voor de codetekst in de programText constante te bouwen. Voeg de volgende regel toe aan uw Main methode:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Met deze twee lijnen wordt de structuur gemaakt en wordt het hoofdknooppunt van die structuur opgehaald. U kunt nu de knooppunten in de structuur onderzoeken. Voeg deze regels toe aan uw Main methode om enkele eigenschappen van het hoofdknooppunt in de structuur weer te geven:

WriteLine($"The tree is a {root.Kind()} node.");
WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using statements. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
    WriteLine($"\t{element.Name}");

Voer de toepassing uit om te zien wat uw code heeft gedetecteerd over het hoofdknooppunt in deze structuur.

Normaal gesproken doorkruist u de structuur voor meer informatie over de code. In dit voorbeeld analyseert u code die u kent om de API's te verkennen. Voeg de volgende code toe om het eerste lid van het root knooppunt te onderzoeken:

MemberDeclarationSyntax firstMember = root.Members[0];
WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;

Dat lid is een Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax. Het vertegenwoordigt alles binnen het bereik van de namespace HelloWorld verklaring. Voeg de volgende code toe om te onderzoeken welke knooppunten in de HelloWorld naamruimte worden gedeclareerd:

WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");

Voer het programma uit om te zien wat u hebt geleerd.

Nu u weet dat de declaratie een Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntaxis, declareert u een nieuwe variabele van dat type om de klassedeclaratie te onderzoeken. Deze klasse bevat slechts één lid: de Main methode. Voeg de volgende code toe om de Main methode te vinden en cast deze naar een Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax.

var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
WriteLine($"There are {programDeclaration.Members.Count} members declared in the {programDeclaration.Identifier} class.");
WriteLine($"The first member is a {programDeclaration.Members[0].Kind()}.");
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];

Het knooppunt methodedeclaratie bevat alle syntactische informatie over de methode. Laten we het retourtype van de Main methode, het aantal en de typen argumenten en de hoofdtekst van de methode weergeven. Voeg de volgende code toe:

WriteLine($"The return type of the {mainDeclaration.Identifier} method is {mainDeclaration.ReturnType}.");
WriteLine($"The method has {mainDeclaration.ParameterList.Parameters.Count} parameters.");
foreach (ParameterSyntax item in mainDeclaration.ParameterList.Parameters)
    WriteLine($"The type of the {item.Identifier} parameter is {item.Type}.");
WriteLine($"The body text of the {mainDeclaration.Identifier} method follows:");
WriteLine(mainDeclaration.Body?.ToFullString());

var argsParameter = mainDeclaration.ParameterList.Parameters[0];

Voer het programma uit om alle informatie te zien die u over dit programma hebt ontdekt:

The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using statements. They are:
        System
        System.Collections
        System.Linq
        System.Text
The first member is a NamespaceDeclaration.
There are 1 members declared in this namespace.
The first member is a ClassDeclaration.
There are 1 members declared in the Program class.
The first member is a MethodDeclaration.
The return type of the Main method is void.
The method has 1 parameters.
The type of the args parameter is string[].
The body text of the Main method follows:
        {
            Console.WriteLine("Hello, World!");
        }

Querymethoden

Naast het doorkruisen van structuren kunt u ook de syntaxisstructuur verkennen met behulp van de querymethoden die zijn gedefinieerd in Microsoft.CodeAnalysis.SyntaxNode. Deze methoden moeten onmiddellijk bekend zijn voor iedereen die bekend is met XPath. U kunt deze methoden met LINQ gebruiken om snel dingen in een boomstructuur te vinden. De SyntaxNode heeft querymethoden zoals DescendantNodes, AncestorsAndSelf en ChildNodes.

U kunt deze querymethoden gebruiken om het argument voor de Main methode te vinden als alternatief voor het navigeren door de structuur. Voeg de volgende code toe aan de onderkant van uw Main methode:

var firstParameters = from methodDeclaration in root.DescendantNodes()
                                        .OfType<MethodDeclarationSyntax>()
                      where methodDeclaration.Identifier.ValueText == "Main"
                      select methodDeclaration.ParameterList.Parameters.First();

var argsParameter2 = firstParameters.Single();

WriteLine(argsParameter == argsParameter2);

De eerste instructie maakt gebruik van een LINQ-expressie en de DescendantNodes methode om dezelfde parameter te vinden als in het vorige voorbeeld.

Voer het programma uit en u kunt zien dat de LINQ-expressie dezelfde parameter heeft gevonden als het handmatig navigeren in de structuur.

In het voorbeeld worden instructies gebruikt WriteLine om informatie weer te geven over de syntaxisstructuren die worden doorkruist. U kunt ook veel meer te weten komen door het voltooide programma uit te voeren onder het foutopsporingsprogramma. U kunt meer van de eigenschappen en methoden onderzoeken die deel uitmaken van de syntaxisstructuur die is gemaakt voor het hello world-programma.

Syntaxis-uitlopers

Vaak wilt u alle knooppunten van een specifiek type in een syntaxisstructuur vinden, bijvoorbeeld elke eigenschapsdeclaratie in een bestand. Door de Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker klasse uit te breiden en de VisitPropertyDeclaration(PropertyDeclarationSyntax) methode te overschrijven, verwerkt u elke eigenschapsdeclaratie in een syntaxisstructuur zonder de structuur van tevoren te kennen. CSharpSyntaxWalker is een specifiek type CSharpSyntaxVisitor dat recursief een knooppunt en elk van de onderliggende items bezoekt.

In dit voorbeeld wordt een CSharpSyntaxWalker geïmplementeerd waarmee een syntaxisstructuur wordt onderzocht. Het verzamelt using instructies die worden gevonden die geen naamruimte importeren System .

Maak een nieuw C# Stand-Alone Code Analysis Tool-project ; Geef deze de naam 'SyntaxWalker'.

U kunt de voltooide code voor dit voorbeeld bekijken in onze GitHub-opslagplaats. Het voorbeeld op GitHub bevat beide projecten die in deze zelfstudie worden beschreven.

Net als in het vorige voorbeeld kunt u een tekenreeksconstante definiëren voor de tekst van het programma dat u gaat analyseren:

        const string programText =
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace TopLevel
{
    using Microsoft;
    using System.ComponentModel;

    namespace Child1
    {
        using Microsoft.Win32;
        using System.Runtime.InteropServices;

        class Foo { }
    }

    namespace Child2
    {
        using System.CodeDom;
        using Microsoft.CSharp;

        class Bar { }
    }
}";

Deze brontekst bevat using instructies verspreid over vier verschillende locaties: op bestandsniveau, in de naamruimte op het hoogste niveau en in de twee geneste naamruimten. In dit voorbeeld wordt een kernscenario uitgelicht voor het gebruik van de CSharpSyntaxWalker klasse voor het uitvoeren van query's op code. Het zou lastig zijn om elk knooppunt in de hoofdsyntaxisstructuur te bezoeken om te zoeken met behulp van declaraties. In plaats daarvan maakt u een afgeleide klasse en overschrijft u de methode die alleen wordt aangeroepen wanneer het huidige knooppunt in de structuur een using-instructie is. Uw bezoeker doet geen werk op andere knooppunttypen. Met deze ene methode worden alle using -instructies onderzocht en wordt een verzameling van de naamruimten gemaakt die zich niet in de System naamruimte bevinden. U bouwt een CSharpSyntaxWalker waarmee alle using instructies worden onderzocht, maar alleen de using -instructies.

Nu u de programmatekst hebt gedefinieerd, moet u een SyntaxTree maken en de hoofdmap van die structuur ophalen:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Maak vervolgens een nieuwe klasse. Kies in Visual Studio De optie Project>Nieuw item toevoegen. Typ usingCollector.cs als bestandsnaam in het dialoogvenster Nieuw item toevoegen.

U implementeert de using bezoekersfunctionaliteit in de UsingCollector klasse. Begin door de UsingCollector klasse af te leiden van CSharpSyntaxWalker.

class UsingCollector : CSharpSyntaxWalker

U hebt opslag nodig voor de naamruimteknooppunten die u verzamelt. Declareer een openbare alleen-lezen-eigenschap in de UsingCollector klasse. U gebruikt deze variabele om de UsingDirectiveSyntax gevonden knooppunten op te slaan:

public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();

De basisklasse CSharpSyntaxWalker implementeert de logica om elk knooppunt in de syntaxisstructuur te bezoeken. De afgeleide klasse overschrijft de methoden die worden aangeroepen voor de specifieke knooppunten waarin u geïnteresseerd bent. In dit geval bent u geïnteresseerd in elke using instructie. Dit betekent dat u de VisitUsingDirective(UsingDirectiveSyntax) methode moet overschrijven. Het enige argument voor deze methode is een Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax -object. Dat is een belangrijk voordeel van het gebruik van de bezoekers: ze roepen de overschreven methoden aan met argumenten die al naar het specifieke knooppunttype zijn gecast. De Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax klasse heeft een Name eigenschap die de naam opslaat van de naamruimte die wordt geïmporteerd. Het is een Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Voeg de volgende code toe in de VisitUsingDirective(UsingDirectiveSyntax) onderdrukking:

public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
    WriteLine($"\tVisitUsingDirective called with {node.Name}.");
    if (node.Name.ToString() != "System" &&
        !node.Name.ToString().StartsWith("System."))
    {
        WriteLine($"\t\tSuccess. Adding {node.Name}.");
        this.Usings.Add(node);
    }
}

Net als in het eerdere voorbeeld hebt u verschillende WriteLine verklaringen toegevoegd om inzicht te krijgen in deze methode. U kunt zien wanneer deze wordt aangeroepen en welke argumenten er elke keer aan worden doorgegeven.

Ten slotte moet u twee regels code toevoegen om de UsingCollector te maken en het hoofdknooppunt te laten bezoeken, waar alle using instructies worden verzameld. Voeg vervolgens een foreach lus toe om alle using instructies weer te geven die uw collector heeft gevonden:

var collector = new UsingCollector();
collector.Visit(root);
foreach (var directive in collector.Usings)
{
    WriteLine(directive.Name);
}

Compileer het programma en voer het uit. U moet de volgende uitvoer zien:

        VisitUsingDirective called with System.
        VisitUsingDirective called with System.Collections.Generic.
        VisitUsingDirective called with System.Linq.
        VisitUsingDirective called with System.Text.
        VisitUsingDirective called with Microsoft.CodeAnalysis.
                Success. Adding Microsoft.CodeAnalysis.
        VisitUsingDirective called with Microsoft.CodeAnalysis.CSharp.
                Success. Adding Microsoft.CodeAnalysis.CSharp.
        VisitUsingDirective called with Microsoft.
                Success. Adding Microsoft.
        VisitUsingDirective called with System.ComponentModel.
        VisitUsingDirective called with Microsoft.Win32.
                Success. Adding Microsoft.Win32.
        VisitUsingDirective called with System.Runtime.InteropServices.
        VisitUsingDirective called with System.CodeDom.
        VisitUsingDirective called with Microsoft.CSharp.
                Success. Adding Microsoft.CSharp.
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
Press any key to continue . . .

Gefeliciteerd U hebt de Syntaxis-API gebruikt om specifieke soorten C#-instructies en -declaraties in C#-broncode te zoeken.