Delen via


Aan de slag met semantische analyse

In deze zelfstudie wordt ervan uitgegaan dat u bekend bent met de syntaxis-API. Het artikel Aan de slag met syntaxisanalyse biedt voldoende inleiding.

In deze zelfstudie verkent u de Symbool- en Binding-API's. Deze API's bieden informatie over de semantische betekenis van een programma. Hiermee kunt u vragen stellen en beantwoorden over de typen die worden vertegenwoordigd door een symbool in uw programma.

U moet 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 deze selecteren als een optioneel onderdeel.

  1. Visual Studio Installer uitvoeren
  2. Selecteer Wijzigen
  3. Controleer de workload voor de ontwikkeling 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.

U wilt desgewenst ook dat de DGML-editor grafieken weergeeft in de visualizer:

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

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, build tools en runtimes .

U wilt desgewenst ook dat de DGML-editor grafieken weergeeft in de visualizer:

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

Informatie over compilaties en symbolen

Naarmate u meer met de .NET Compiler SDK werkt, raakt u vertrouwd met het onderscheid tussen syntaxis-API en de Semantische API. Met de syntaxis-API kunt u de structuur van een programma bekijken. Vaak wilt u echter uitgebreidere informatie over de semantiek of betekenis van een programma. Hoewel een los codebestand of codefragment van Visual Basic of C#-code afzonderlijk syntactisch kan worden geanalyseerd, is het niet zinvol om vragen zoals 'wat is het type van deze variabele' in een vacuüm te stellen. De betekenis van een typenaam kan afhankelijk zijn van assemblyverwijzingen, naamruimteimporten of andere codebestanden. Deze vragen worden beantwoord met behulp van de semantische API, met name de Microsoft.CodeAnalysis.Compilation klasse.

Een exemplaar van Compilation is vergelijkbaar met één project zoals gezien door de compiler en vertegenwoordigt alles wat nodig is voor het compileren van een Visual Basic- of C#-programma. De compilatie bevat de set bronbestanden die moeten worden gecompileerd, assemblyverwijzingen en compileropties. U kunt met behulp van alle andere informatie in deze context berde opgeven wat de betekenis van de code is. Met A Compilation kunt u Symbolen vinden- entiteiten zoals typen, naamruimten, leden en variabelen waarnaar namen en andere expressies verwijzen. Het proces voor het koppelen van namen en expressies aan Symbolen wordt Binding genoemd.

Net als Microsoft.CodeAnalysis.SyntaxTree, Compilation is een abstracte klasse met taalspecifieke derivaten. Wanneer u een exemplaar van Compilatie maakt, moet u een factory-methode voor de Microsoft.CodeAnalysis.CSharp.CSharpCompilation klasse (of Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation) aanroepen.

Query's uitvoeren op symbolen

In deze zelfstudie bekijkt u het programma 'Hallo wereld' opnieuw. Deze keer voert u een query uit op de symbolen in het programma om te begrijpen welke typen deze symbolen vertegenwoordigen. U voert een query uit op de typen in een naamruimte en leert de methoden te vinden die beschikbaar zijn voor een type.

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

Notitie

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 verzamelingsleden worden gecast 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# -project zelfstandig hulpprogramma voor codeanalyse :

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

U gaat het basisprogramma '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.Generic;
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();

Bouw vervolgens een CSharpCompilation op basis van de structuur die u al hebt gemaakt. Het voorbeeld 'Hallo wereld' is afhankelijk van de String typen enConsole. U moet verwijzen naar de assembly die deze twee typen declareert in uw compilatie. Voeg de volgende regel toe aan uw Main methode om een compilatie van de syntaxisstructuur te maken, inclusief de verwijzing naar de juiste assembly:

var compilation = CSharpCompilation.Create("HelloWorld")
    .AddReferences(MetadataReference.CreateFromFile(
        typeof(string).Assembly.Location))
    .AddSyntaxTrees(tree);

Met de CSharpCompilation.AddReferences methode worden verwijzingen naar de compilatie toegevoegd. Met de MetadataReference.CreateFromFile methode wordt een assembly als referentie geladen.

Een query uitvoeren op het semantische model

Zodra u een Compilation hebt, kunt u deze voor een SemanticModel vragen voor elke SyntaxTree in die Compilation. U kunt het semantische model beschouwen als de bron voor alle informatie die u normaal gesproken van IntelliSense krijgt. Een SemanticModel kan vragen beantwoorden als 'Welke namen vallen binnen het bereik op deze locatie?', 'Welke leden zijn toegankelijk via deze methode?', 'Welke variabelen worden gebruikt in dit tekstblok?' en 'Waarnaar verwijst deze naam/expressie?' Voeg deze instructie toe om het semantische model te maken:

SemanticModel model = compilation.GetSemanticModel(tree);

Een naam binden

De Compilation maakt de SemanticModel op basis van de SyntaxTree. Nadat u het model hebt gemaakt, kunt u er een query op uitvoeren om de eerste using instructie te vinden en de symboolgegevens voor de System naamruimte op te halen. Voeg deze twee regels toe aan uw Main methode om het semantische model te maken en het symbool voor de eerste instructie op te halen:

// Use the syntax tree to find "using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;

// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);

De voorgaande code laat zien hoe u de naam in de eerste using instructie verbindt om een Microsoft.CodeAnalysis.SymbolInfo op te halen voor de System naamruimte. De voorgaande code illustreert ook dat u het syntaxismodel gebruikt om de structuur van de code te vinden; u het semantische model gebruikt om de betekenis ervan te begrijpen. Het syntaxismodel vindt de tekenreeks System in de using-instructie. Het semantische model bevat alle informatie over de typen die zijn gedefinieerd in de System naamruimte.

Van het SymbolInfo -object kunt u de Microsoft.CodeAnalysis.ISymbol verkrijgen met behulp van de SymbolInfo.Symbol eigenschap . Deze eigenschap retourneert het symbool waarnaar deze expressie verwijst. Voor expressies die nergens naar verwijzen (zoals numerieke letterlijke waarden) is nulldeze eigenschap . Wanneer de SymbolInfo.Symbol niet null is, geeft de ISymbol.Kind het type van het symbool aan. In dit voorbeeld is de ISymbol.Kind eigenschap een SymbolKind.Namespace. Voeg de volgende code toe aan uw Main methode. Het symbool voor de System naamruimte wordt opgehaald en vervolgens worden alle onderliggende naamruimten weergegeven die in de System naamruimte zijn gedeclareerd:

var systemSymbol = (INamespaceSymbol?)nameInfo.Symbol;
if (systemSymbol?.GetNamespaceMembers() is not null)
{
    foreach (INamespaceSymbol ns in systemSymbol?.GetNamespaceMembers()!)
    {
        Console.WriteLine(ns);
    }
}

Voer het programma uit. Als het goed is, ziet u de volgende uitvoer:

System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .

Notitie

De uitvoer bevat niet elke naamruimte die een onderliggende naamruimte van de System naamruimte is. Hiermee wordt elke naamruimte weergegeven die aanwezig is in deze compilatie, die alleen verwijst naar de assembly waar System.String is gedeclareerd. Naamruimten die in andere assembly's zijn gedeclareerd, zijn niet bekend bij deze compilatie

Een expressie binden

De voorgaande code laat zien hoe u een symbool kunt vinden door een naam te koppelen. Er zijn andere expressies in een C#-programma die kunnen worden gebonden en die geen namen zijn. Om deze mogelijkheid te demonstreren, gaan we de binding openen met een eenvoudige letterlijke tekenreeks.

Het programma 'Hallo wereld' bevat een Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax, de tekenreeks 'Hallo wereld!' die wordt weergegeven op de console.

U vindt de tekenreeks 'Hallo wereld!' door de letterlijke tekenreeks in het programma te zoeken. Zodra u het syntaxisknooppunt hebt gevonden, haalt u de typegegevens voor dat knooppunt op uit het semantische model. Voeg de volgende code toe aan uw Main methode:

// Use the syntax model to find the literal string:
LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();

// Use the semantic model for type information:
TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

De Microsoft.CodeAnalysis.TypeInfo struct bevat een TypeInfo.Type eigenschap die toegang tot de semantische informatie over het type letterlijke waarde mogelijk maakt. In dit voorbeeld is dat het string type. Voeg een declaratie toe waarmee deze eigenschap wordt toegewezen aan een lokale variabele:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Om deze zelfstudie te voltooien, gaan we een LINQ-query maken waarmee een reeks van alle openbare methoden wordt gemaakt die zijn gedeclareerd voor het string type dat een stringretourneert. Deze query wordt complex, dus we gaan deze regel voor regel bouwen en deze vervolgens als één query reconstrueren. De bron voor deze query is de volgorde van alle leden die zijn gedeclareerd voor het string type:

var allMembers = stringTypeSymbol?.GetMembers();

Deze bronreeks bevat alle leden, inclusief eigenschappen en velden, dus filter deze met behulp van de ImmutableArray<T>.OfType methode om elementen te vinden die objecten zijn Microsoft.CodeAnalysis.IMethodSymbol :

var methods = allMembers?.OfType<IMethodSymbol>();

Voeg vervolgens nog een filter toe om alleen de openbare methoden te retourneren en een stringte retourneren:

var publicStringReturningMethods = methods?
    .Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType, stringTypeSymbol) &&
    m.DeclaredAccessibility == Accessibility.Public);

Selecteer alleen de naameigenschap en alleen afzonderlijke namen door eventuele overbelastingen te verwijderen:

var distinctMethods = publicStringReturningMethods?.Select(m => m.Name).Distinct();

U kunt ook de volledige query maken met behulp van de LINQ-querysyntaxis en vervolgens alle methodenamen weergeven in de -console:

foreach (string name in (from method in stringTypeSymbol?
                         .GetMembers().OfType<IMethodSymbol>()
                         where SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol) &&
                         method.DeclaredAccessibility == Accessibility.Public
                         select method.Name).Distinct())
{
    Console.WriteLine(name);
}

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

Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .

U hebt de semantische API gebruikt om informatie te zoeken en weer te geven over de symbolen die deel uitmaken van dit programma.