Share via


Kom igång med semantisk analys

Den här självstudien förutsätter att du är bekant med syntax-API:et. Artikeln Komma igång med syntaxanalys ger tillräcklig introduktion.

I den här självstudien utforskar du API:ernasymbol och bindning. Dessa API:er innehåller information om den semantiska innebörden av ett program. De gör att du kan ställa och svara på frågor om de typer som representeras av någon symbol i ditt program.

Du måste installera .NET Compiler Platform SDK:

Installationsinstruktioner – Installationsprogram för Visual Studio

Det finns två olika sätt att hitta .NET Compiler Platform SDK i Visual Studio Installer:

Installera med Visual Studio Installer – arbetsbelastningsvyn

.NET Compiler Platform SDK väljs inte automatiskt som en del av arbetsbelastningen för Utveckling av Visual Studio-tillägg. Du måste välja den som en valfri komponent.

  1. Köra Installationsprogrammet för Visual Studio
  2. Välj Ändra
  3. Kontrollera arbetsbelastningen för Utveckling av Visual Studio-tillägg .
  4. Öppna visual studiotilläggets utvecklingsnod i sammanfattningsträdet.
  5. Markera kryssrutan för .NET Compiler Platform SDK. Du hittar den sist under de valfria komponenterna.

Du vill också att DGML-redigeraren ska visa grafer i visualiseraren:

  1. Öppna noden Enskilda komponenter i sammanfattningsträdet.
  2. Markera kryssrutan för DGML-redigeraren

Installera med hjälp av fliken Installationsprogram för Visual Studio – enskilda komponenter

  1. Köra Installationsprogrammet för Visual Studio
  2. Välj Ändra
  3. Välj fliken Enskilda komponenter
  4. Markera kryssrutan för .NET Compiler Platform SDK. Du hittar den längst upp under avsnittet Kompilatorer, byggverktyg och körning.

Du vill också att DGML-redigeraren ska visa grafer i visualiseraren:

  1. Markera kryssrutan för DGML-redigeraren. Du hittar den under avsnittet Kodverktyg .

Förstå kompileringar och symboler

När du arbetar mer med .NET Compiler SDK bekantar du dig med skillnaderna mellan Syntax-API:et och semantik-API:et. Med syntax-API:et kan du titta på strukturen för ett program. Men ofta vill du ha mer information om semantiken eller innebörden av ett program. Även om en lös kodfil eller kodfragment med Visual Basic- eller C#-kod kan analyseras syntaktiskt isolerat, är det inte meningsfullt att ställa frågor som "vilken typ av variabel" i ett vakuum. Innebörden av ett typnamn kan vara beroende av sammansättningsreferenser, namnområdesimporter eller andra kodfiler. Dessa frågor besvaras med hjälp av semantik-API:et Microsoft.CodeAnalysis.Compilation , särskilt klassen.

En instans av Compilation motsvarar ett enskilt projekt som kompilatorn ser och representerar allt som behövs för att kompilera ett Visual Basic- eller C#-program. Kompilering innehåller en uppsättning källfiler som ska kompileras, sammansättningsreferenser och kompilatoralternativ. Du kan resonera om kodens innebörd med hjälp av all annan information i den här kontexten. Med Compilation A kan du hitta symboler – entiteter som typer, namnrymder, medlemmar och variabler som namn och andra uttryck refererar till. Processen för att associera namn och uttryck med symboler kallas bindning.

Precis som Microsoft.CodeAnalysis.SyntaxTreeär Compilation en abstrakt klass med språkspecifika derivat. När du skapar en kompileringsinstans måste du anropa en fabriksmetod i Microsoft.CodeAnalysis.CSharp.CSharpCompilation klassen (eller Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Köra frågor mot symboler

I den här självstudien tittar du på programmet "Hello World" igen. Den här gången frågar du symbolerna i programmet för att förstå vilka typer dessa symboler representerar. Du frågar efter typerna i ett namnområde och lär dig att hitta de metoder som är tillgängliga för en typ.

Du kan se den färdiga koden för det här exemplet på vår GitHub-lagringsplats.

Anteckning

Syntaxträdstyperna använder arv för att beskriva de olika syntaxelement som är giltiga på olika platser i programmet. Om du använder dessa API:er innebär det ofta att du gjuter egenskaper eller samlingsmedlemmar till specifika härledda typer. I följande exempel är tilldelningen och avgjutningarna separata instruktioner med hjälp av uttryckligen inskrivna variabler. Du kan läsa koden för att se returtyperna för API:et och körningstypen för de objekt som returneras. I praktiken är det vanligare att använda implicit skrivna variabler och förlita sig på API-namn för att beskriva typen av objekt som undersöks.

Skapa ett nytt C#-projekt för fristående kodanalysverktyg :

  • I Visual Studio väljer du Nyttprojekt för>> att visa dialogrutan Nytt projekt.
  • Under Visual C#>Utökningsbarhet väljer du Fristående kodanalysverktyg.
  • Ge projektet namnet "SemanticQuickStart" och klicka på OK.

Du ska analysera det grundläggande programmet "Hello World!" som visades tidigare. Lägg till texten för Hello World-programmet som en konstant i klassenProgram:

        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!"");
        }
    }
}";

Lägg sedan till följande kod för att skapa syntaxträdet för kodtexten i konstanten programText . Lägg till följande rad i din Main metod:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Skapa sedan en CSharpCompilation från trädet som du redan har skapat. Exemplet "Hello World" förlitar sig på typerna String ochConsole. Du måste referera till sammansättningen som deklarerar dessa två typer i din kompilering. Lägg till följande rad i Main metoden för att skapa en kompilering av syntaxträdet, inklusive referensen till lämplig sammansättning:

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

Metoden CSharpCompilation.AddReferences lägger till referenser till kompilering. Metoden MetadataReference.CreateFromFile läser in en sammansättning som referens.

Köra frågor mot semantikmodellen

När du har en Compilation kan du be om en SemanticModel för någon SyntaxTree som finns i det Compilation. Du kan se semantikmodellen som källa för all information som du normalt får från intellisense. A SemanticModel kan svara på frågor som "Vilka namn finns i omfånget på den här platsen?", "Vilka medlemmar är tillgängliga från den här metoden?", "Vilka variabler används i det här textblocket?", och "Vad refererar det här namnet/uttrycket till?" Lägg till den här instruktionen för att skapa semantikmodellen:

SemanticModel model = compilation.GetSemanticModel(tree);

Binda ett namn

Compilation Skapar SemanticModel från SyntaxTree. När du har skapat modellen kan du fråga den för att hitta det första using direktivet och hämta symbolinformationen System för namnområdet. Lägg till dessa två rader i Main metoden för att skapa den semantiska modellen och hämta symbolen för den första med -instruktionen:

// 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);

Koden ovan visar hur du binder namnet i det första using direktivet för att hämta en Microsoft.CodeAnalysis.SymbolInfo för System namnområdet. Föregående kod visar också att du använder syntaxmodellen för att hitta kodens struktur. du använder den semantiska modellen för att förstå dess innebörd. Syntaxmodellen hittar strängen System i instruktionen using. Semantikmodellen har all information om de typer som definierats i System namnområdet.

Från objektet SymbolInfo kan du hämta Microsoft.CodeAnalysis.ISymbol med hjälp av SymbolInfo.Symbol egenskapen . Den här egenskapen returnerar den symbol som det här uttrycket refererar till. För uttryck som inte refererar till något (till exempel numeriska literaler) är nullden här egenskapen . SymbolInfo.Symbol När är inte nullISymbol.Kind, anger den typ av symbol. I det här exemplet är egenskapen ISymbol.Kind en SymbolKind.Namespace. Lägg till följande kod i din Main metod. Den hämtar symbolen System för namnområdet och visar sedan alla underordnade namnområden som deklarerats i System namnområdet:

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

Kör programmet så bör du se följande utdata:

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 . . .

Anteckning

Utdata innehåller inte alla namnområden som är ett underordnat namnområde i System namnområdet. Den visar varje namnområde som finns i den här kompilering, som endast refererar till sammansättningen där System.String deklareras. Alla namnområden som deklareras i andra sammansättningar är inte kända för den här kompileringsuppsättningen

Binda ett uttryck

Koden ovan visar hur du hittar en symbol genom att binda till ett namn. Det finns andra uttryck i ett C#-program som kan bindas som inte är namn. För att demonstrera den här funktionen ska vi komma åt bindningen till en enkel strängliteral.

Programmet "Hello World" innehåller strängen Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax"Hello, World!" som visas i konsolen.

Du hittar strängen "Hello, World!" genom att hitta strängliteralen i programmet. När du har hittat syntaxnoden hämtar du sedan typinformationen för noden från semantikmodellen. Lägg till följande kod i din Main -metod:

// 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);

Struct Microsoft.CodeAnalysis.TypeInfo innehåller en TypeInfo.Type egenskap som ger åtkomst till semantisk information om typen av literal. I det här exemplet är det typen string . Lägg till en deklaration som tilldelar den här egenskapen till en lokal variabel:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

För att slutföra den här självstudien ska vi skapa en LINQ-fråga som skapar en sekvens med alla offentliga metoder som deklarerats för den string typ som returnerar en string. Den här frågan blir komplex, så vi skapar den rad för rad och rekonstruerar den sedan som en enda fråga. Källan för den här frågan är sekvensen för alla medlemmar som deklarerats för string typen:

var allMembers = stringTypeSymbol?.GetMembers();

Källsekvensen innehåller alla medlemmar, inklusive egenskaper och fält, så filtrera den med hjälp av ImmutableArray<T>.OfType metoden för att hitta element som är Microsoft.CodeAnalysis.IMethodSymbol objekt:

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

Lägg sedan till ytterligare ett filter för att endast returnera de metoder som är offentliga och returnera ett string:

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

Välj endast namnegenskapen och endast distinkta namn genom att ta bort eventuella överlagringar:

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

Du kan också skapa den fullständiga frågan med hjälp av LINQ-frågesyntaxen och sedan visa alla metodnamn i konsolen:

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);
}

Skapa och kör programmet. Du bör se följande utdata:

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 . . .

Du har använt det semantiska API:et för att hitta och visa information om de symboler som ingår i det här programmet.