Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
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 die ze elk programma van elke grootte kunnen vertegenwoordigen. Deze structuren kunnen volledige programma's beschrijven die correct worden gecompileerd en uitgevoerd. Ze kunnen ook onvolledige programma's beschrijven, zoals u ze schrijft, in de editor.
Om deze uitgebreide expressie mogelijk te maken, 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 volledige tekst vertegenwoordigt één bronbestand of een compilatie-eenheid. De eerste drie regels van dat bronbestand zijn gebruikersrichtlijnen. De resterende bron bevindt zich in een naamruimtedeclaratie. De naamruimtedeclaratie bevat een declaratie van onderliggende klassen. De klassedeclaratie bevat één methodedeclaratie.
De syntaxis-API maakt een boomstructuur met de wortel die de compilatie-eenheid vertegenwoordigt. Knooppunten in de structuur vertegenwoordigen de using instructies, naamruimtedeclaratie en alle andere elementen van het programma. De boomstructuur gaat verder naar de laagste niveaus: de tekenreeks ‘Hallo wereld!’ is een letterlijk tekenreekstoken dat een afgeleide is 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 deze informatie kunt u de code volledig vertegenwoordigen als geschreven en gelezen door menselijke programmeurs of de compiler. Met deze structuur kunt u communiceren met de broncode op een zeer zinvol niveau. 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 Installatieprogramma van Visual Studio:
Installeren met de Visual Studio Installer - Workloads-weergave
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.
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Controleer de ontwikkelworkload van de Visual Studio-extensie .
- Open het ontwikkelknooppunt van de Visual Studio-extensie in de overzichtsstructuur.
- Zorg ervoor dat het selectievakje voor .NET Compiler Platform SDK is ingeschakeld.
- Selecteer Wijzigen.
Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in de visualisatie:
- Open het knooppunt Afzonderlijke onderdelen in de overzichtsstructuur.
- Schakel het selectievakje voor DGML-editor in
Installeren met behulp van het tabblad Afzonderlijke onderdelen van Visual Studio Installer
- Visual Studio Installer uitvoeren
- Selecteer Wijzigen
- Het tabblad Afzonderlijke onderdelen selecteren
- Schakel het selectievakje voor .NET Compiler Platform SDK in. Je vindt het bovenaan in de sectie Compilers, bouwtools en runtimes.
- Selecteer Wijzigen.
Desgewenst wilt u ook dat de DGML-editor grafieken weergeeft in de visualisatie:
- Schakel het selectievakje voor DGML-editor in. U vindt deze in de sectie Codehulpprogramma's .
Inzicht in syntaxisstructuren
U gebruikt de syntaxis-API voor een analyse van de structuur van C#-code. De syntaxis-API maakt de parsers, de syntaxisstructuren en hulpprogramma's beschikbaar voor het analyseren en samenstellen van syntaxisstructuren. Zo zoekt u code naar specifieke syntaxiselementen of leest u de code voor een programma.
Een syntaxisstructuur is een gegevensstructuur die wordt gebruikt door de C#- en Visual Basic-compilers om C# en Visual Basic-programma's te begrijpen. Syntaxisstructuren worden geproduceerd door dezelfde parser die wordt uitgevoerd wanneer een project wordt gebouwd of een ontwikkelaar op F5 drukt. De syntaxisbomen hebben volledige getrouwheid aan de taal; elke informatiebit in een codebestand wordt weergegeven in de boom. Als u een syntaxisstructuur naar tekst schrijft, wordt de exacte oorspronkelijke tekst gereproduceerd die is geparseerd. De syntaxisstructuren zijn ook onveranderbaar; nadat u een syntaxisstructuur hebt gemaakt, kan deze nooit worden gewijzigd. Consumenten van de bomen kunnen de bomen op meerdere threads analyseren, zonder vergrendelingen of andere gelijktijdigheidsmaatregelen, wetende dat de gegevens nooit veranderen. 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:
- De Microsoft.CodeAnalysis.SyntaxTree klasse, een exemplaar waarvan een hele parseringsstructuur vertegenwoordigt. SyntaxTree is een abstracte klasse met taalspecifieke derivaten. U gebruikt de parseringsmethoden van de Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree (of Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree) klasse om tekst te parseren in C# (of Visual Basic).
- De Microsoft.CodeAnalysis.SyntaxNode klasse, waarvan de exemplaren syntactische constructies vertegenwoordigen, zoals declaraties, instructies, zinnen en expressies.
- De Microsoft.CodeAnalysis.SyntaxToken structuur, die een afzonderlijk trefwoord, id, operator of interpunctie vertegenwoordigt.
- En ten slotte de Microsoft.CodeAnalysis.SyntaxTrivia structuur, die syntactisch onbelangrijke stukjes informatie vertegenwoordigt, zoals de witruimte tussen tokens, voorverwerkingsrichtlijnen en opmerkingen.
Trivia, tokens en knooppunten zijn hiërarchisch samengesteld om een structuur te vormen die alles in een fragment van Visual Basic- of C#-code volledig vertegenwoordigt. U kunt deze structuur zien met behulp van het venster Syntaxis visualiseren . Kies in Visual Studio Beeld>Andere Windows>Syntax-visualisator. Het voorgaande C#-bronbestand dat met syntaxis visualiseren wordt onderzocht, ziet er bijvoorbeeld als volgt uit:
SyntaxNode: Blauw | SyntaxToken: Groen | SyntaxTrivia: 
Door deze boomstructuur te navigeren, kunt u elke instructie, uitdrukking, token of stukje witruimte in een codebestand vinden.
Hoewel u alles in een codebestand kunt vinden met behulp van de syntaxis-API's, zijn de meeste scenario's het onderzoeken van kleine codefragmenten of het zoeken naar bepaalde instructies of fragmenten. In de volgende twee voorbeelden ziet u typische toepassingen om door de structuur van code te bladeren of om te zoeken naar enkele instructies.
Doorkruising van bomen
U kunt de knooppunten op twee manieren in een syntaxisstructuur onderzoeken. U kunt de structuur doorlopen om elk knooppunt te onderzoeken of u kunt query's uitvoeren op specifieke elementen of knooppunten.
Handmatig doorkruisen
U kunt de voltooide code voor dit voorbeeld zien in onze GitHub-opslagplaats.
Opmerking
De typen syntaxisstructuur 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 dat eigenschappen of leden van verzamelingen naar specifieke afgeleide typen moeten worden geconverteerd. In de volgende voorbeelden zijn de toewijzing en de casts afzonderlijke verklaringen, 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#>ExtensibilityStand-Alone Code Analysis Tool.
- Geef uw project de naam 'SyntaxTreeManualTraversal' en klik op OK.
U gaat het basisprogramma 'Hallo wereld!' analyseren dat eerder is weergegeven.
Voeg de tekst voor het Hello World-programma toe als een constante in uw Program klas:
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();
Deze twee regels maken de boom en halen het hoofdknooppunt van die boom op. 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 directives. 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 doorloopt u de boomstructuur om inzicht te krijgen in 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 voor de 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 hebt gedetecteerd over dit programma:
The tree is a CompilationUnit node.
The tree has 1 elements in it.
The tree has 4 using directives. 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 bomen, kunt u ook de syntaxisstructuur verkennen met behulp van de querymethoden die zijn gedefinieerd op Microsoft.CodeAnalysis.SyntaxNode. Deze methoden moeten onmiddellijk bekend zijn voor iedereen die bekend is met XPath. U kunt deze methoden gebruiken met LINQ 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 in 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 bij handmatige navigatie door de boomstructuur.
In het voorbeeld worden WriteLine instructies gebruikt om informatie weer te geven over de syntaxisstructuren terwijl ze worden doorkruist. U kunt ook veel meer leren door het voltooide programma uit te voeren onder het foutopsporingsprogramma. U kunt meer eigenschappen en methoden onderzoeken die deel uitmaken van de syntaxisstructuur die is gemaakt voor het hello world-programma.
Syntaxisverkenners
Vaak wilt u alle knooppunten van een specifiek type in een syntaxisstructuur zoeken, 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 ervan vooraf te kennen. CSharpSyntaxWalker is een specifiek type CSharpSyntaxVisitor dat recursief een node bezoekt en elk van de kinderen.
In dit voorbeeld wordt een CSharpSyntaxWalker structuur geïmplementeerd waarmee een syntaxisstructuur wordt onderzocht. Het verzamelt using instructies die niet een System naamruimte importeren.
Een nieuw C# Stand-Alone Code Analysis Tool-project maken; geef deze de naam 'SyntaxWalker'.
U kunt de voltooide code voor dit voorbeeld zien 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 die de tekst van het programma bevat 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: het bestandsniveau, in de naamruimte op het hoogste niveau en in de twee geneste naamruimten. In dit voorbeeld wordt een kernscenario voor het gebruik van de CSharpSyntaxWalker klasse gemarkeerd om query's uit te voeren op code. Het zou lastig zijn om elk knooppunt in de hoofdsyntaxisstructuur te bezoeken om declaraties te vinden. 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 aan andere knooppunttypen. Deze ene methode onderzoekt elk van de using instructies en bouwt een verzameling van de naamruimten die zich niet in de System naamruimte bevinden. U bouwt een CSharpSyntaxWalker die alle using instructies onderzoekt, maar alleen de using instructies.
Nu u de programmatekst hebt gedefinieerd, moet u een SyntaxTree boomstructuur maken en de wortel van die boom ophalen.
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Maak vervolgens een nieuwe klasse. Kies in Visual Studio Project>Add New Item. Typ in het dialoogvenster Nieuw item toevoegenUsingCollector.cs als bestandsnaam.
U implementeert de using bezoekersfunctionaliteit in de UsingCollector klas. Begin met het maken van de UsingCollector klasse afgeleid 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 knooppunten op te slaan die u vindt:
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 een using richtlijn. Dat betekent dat u de VisitUsingDirective(UsingDirectiveSyntax) methode moet overschrijven. Het ene argument voor deze methode is een Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax object. Dat is een belangrijk voordeel van het gebruik van het visitorpatroon: 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 waarin de naam van de naamruimte wordt opgeslagen die wordt geïmporteerd. Het is een Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Voeg de volgende code toe in de VisitUsingDirective(UsingDirectiveSyntax) overschrijving:
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 vorige voorbeeld hebt u verschillende WriteLine instructies toegevoegd om inzicht te krijgen in deze methode. U kunt zien wanneer deze wordt aangeroepen en welke argumenten er telkens aan worden doorgegeven.
Ten slotte moet u twee regels code toevoegen om de UsingCollector te maken, zodat het het hoofdknooppunt bezoekt en alle using instructies verzamelt. Voeg vervolgens een foreach lus toe om alle instructies weer te geven die uw collector using 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 ziet nu de volgende uitvoer:
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 instructies en declaraties in C#-broncode te vinden.