Začínáme se sémantickou analýzou

V tomto kurzu se předpokládá, že znáte rozhraní API pro syntaxi. Dostatečný úvod najdete v článku Začínáme s analýzou syntaxe .

V tomto kurzu prozkoumáte rozhraní API symbolů a vazeb. Tato rozhraní API poskytují informace o sémantickém významu programu. Umožňují vám klást otázky týkající se typů reprezentovaných libovolným symbolem v programu a odpovídat na ně.

Budete si muset nainstalovat sadu .NET Compiler Platform SDK:

Pokyny k instalaci – Instalační program pro Visual Studio

Sadu .NET Compiler Platform SDK v Instalační program pro Visual Studio najdete dvěma různými způsoby:

Instalace pomocí zobrazení Instalační program pro Visual Studio – Úlohy

Sada .NET Compiler Platform SDK není automaticky vybrána jako součást úlohy vývoj rozšíření sady Visual Studio. Musíte ho vybrat jako volitelnou komponentu.

  1. Spuštění Instalační program pro Visual Studio
  2. Vyberte Upravit.
  3. Projděte si úlohu Vývoj rozšíření sady Visual Studio .
  4. Otevřete uzel Vývoj rozšíření sady Visual Studio ve stromu souhrnu.
  5. Zaškrtněte políčko pro .NET Compiler Platform SDK. Najdete ho jako poslední pod volitelnými komponentami.

Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:

  1. Otevřete uzel Jednotlivé komponenty ve stromu souhrnu.
  2. Zaškrtněte políčko editoru DGML.

Instalace pomocí karty Instalační program pro Visual Studio – Jednotlivé komponenty

  1. Spuštění Instalační program pro Visual Studio
  2. Vyberte Upravit.
  3. Vyberte kartu Jednotlivé komponenty .
  4. Zaškrtněte políčko pro .NET Compiler Platform SDK. Najdete ji v horní části v části Kompilátory, nástroje sestavení a moduly runtime .

Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:

  1. Zaškrtněte políčko Editor DGML. Najdete ho v části Nástroje pro kód .

Principy kompilací a symbolů

Při další práci se sadou .NET Compiler SDK se seznámíte s rozdíly mezi rozhraním Api syntaxe a sémantickým rozhraním API. Syntaxe rozhraní API umožňuje podívat se na strukturu programu. Často ale potřebujete bohatší informace o sémantice nebo významu programu. I když se soubor volného kódu nebo fragment kódu jazyka Visual Basic nebo C# dá syntakticky analyzovat izolovaně, nemá smysl ve vakuu pokládat otázky typu "jaký je typ této proměnné". Význam názvu typu může být závislý na odkazech na sestavení, importech oboru názvů nebo jiných souborech kódu. Odpovědi na tyto otázky jsou zodpovězeny pomocí sémantickéhoMicrosoft.CodeAnalysis.Compilation rozhraní API, konkrétně třídy .

Instance Compilation je obdobou jednoho projektu, jak ho vidí kompilátor, a představuje vše potřebné ke kompilaci programu jazyka Visual Basic nebo C#. Kompilace zahrnuje sadu zdrojových souborů, které mají být zkompilovány, odkazy na sestavení a možnosti kompilátoru. O významu kódu můžete uvažovat pomocí všech ostatních informací v tomto kontextu. Umožňuje Compilation najít symboly – entity, jako jsou typy, obory názvů, členy a proměnné, na které odkazují názvy a další výrazy. Proces přidružování názvů a výrazů k symbolům se nazývá Vazba.

Podobně jako Microsoft.CodeAnalysis.SyntaxTreeje třída Compilation abstraktní třídy s odvozenými pro konkrétní jazyk. Při vytváření instance compilation musíte vyvolat metodu factory ve Microsoft.CodeAnalysis.CSharp.CSharpCompilation třídě (nebo Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Dotazování symbolů

V tomto kurzu se znovu podíváte na program "Hello World". Tentokrát se na symboly v programu dotazujete, abyste pochopili, jaké typy tyto symboly představují. Dotazujete se na typy v oboru názvů a zjistíte, jak najít metody dostupné pro typ.

Hotový kód pro tuto ukázku najdete v našem úložišti GitHub.

Poznámka

Typy stromu syntaxe používají dědičnost k popisu různých prvků syntaxe, které jsou platné v různých umístěních v programu. Použití těchto rozhraní API často znamená přetypování vlastností nebo členů kolekce na konkrétní odvozené typy. V následujících příkladech jsou přiřazení a přetypování samostatné příkazy, které používají proměnné s explicitně zadanými typy. Můžete si přečíst kód a zobrazit návratové typy rozhraní API a typ modulu runtime vrácených objektů. V praxi je častější používat implicitně typované proměnné a spoléhat se na názvy rozhraní API, které popisují typ zkoumaných objektů.

Vytvořte nový projekt samostatného nástroje pro analýzu kódu v jazyce C#:

  • V sadě Visual Studio zvolte Soubor>nový>projekt , aby se zobrazilo dialogové okno Nový projekt.
  • V části Rozšiřitelnost jazykaVisual C#>zvolte Samostatný nástroj pro analýzu kódu.
  • Projekt pojmenujte "SemanticQuickStart" a klikněte na OK.

Budete analyzovat základní program "Hello World!" zobrazený dříve. Přidejte text pro Hello World program jako konstantu ve tříděProgram:

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

Dále přidejte následující kód, který vytvoří strom syntaxe pro text kódu v konstantě programText . Do metody Main přidejte následující řádek:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Dále vytvořte ze stromu, který CSharpCompilation jste už vytvořili. Ukázka "Hello World" spoléhá na String typy a Console . Je třeba odkazovat na sestavení, které deklaruje tyto dva typy v kompilaci. Přidáním následujícího řádku do metody Main vytvořte kompilaci stromu syntaxe, včetně odkazu na příslušné sestavení:

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

Metoda CSharpCompilation.AddReferences přidá odkazy na kompilaci. Metoda MetadataReference.CreateFromFile načte sestavení jako odkaz.

Dotazování sémantického modelu

Jakmile budete mít objekt , Compilation můžete ho požádat o za SemanticModel jakýkoliv SyntaxTree z nich obsažených v Compilation. Sémantický model si můžete představit jako zdroj všech informací, které byste normálně získali z IntelliSense. Může SemanticModel odpovědět na otázky jako "Jaká jména jsou v tomto umístění v oboru?", "Jaké členy jsou přístupné z této metody?", "Jaké proměnné se používají v tomto bloku textu?" a "K čemu tento název/výraz odkazuje?" Přidáním tohoto příkazu vytvořte sémantický model:

SemanticModel model = compilation.GetSemanticModel(tree);

Vytvoření vazby názvu

Vytvoří Compilation z SemanticModel .SyntaxTree Po vytvoření modelu můžete dotazem najít první using direktivu a načíst informace o symbolu System pro obor názvů. Přidáním těchto dvou řádků do metody Main vytvořte sémantický model a načtěte symbol pro první příkaz using:

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

Předchozí kód ukazuje, jak vytvořit vazbu názvu v první using direktivě na načtení Microsoft.CodeAnalysis.SymbolInfo pro System obor názvů. Předchozí kód také ukazuje, že k vyhledání struktury kódu použijete model syntaxe ; k pochopení jeho významu použijete sémantický model . Model syntaxe najde řetězec System v příkazu using. Sémantický model obsahuje všechny informace o typech definovaných System v oboru názvů .

Z objektu SymbolInfo můžete získat Microsoft.CodeAnalysis.ISymbol pomocí SymbolInfo.Symbol vlastnosti . Tato vlastnost vrátí symbol, na který tento výraz odkazuje. Pro výrazy, které neodkazují na nic (například číselné literály), je nulltato vlastnost . SymbolInfo.Symbol Pokud není null, ISymbol.Kind označuje typ symbolu. V tomto příkladu ISymbol.Kind je vlastnost .SymbolKind.Namespace Do metody Main přidejte následující kód. Načte symbol pro System obor názvů a zobrazí všechny podřízené obory názvů deklarované v oboru System názvů:

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

Spusťte program a měl by se zobrazit následující výstup:

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

Poznámka

Výstup neobsahuje všechny obory názvů, které jsou podřízenými obory System názvů oboru názvů. Zobrazí všechny obory názvů, které jsou přítomny v této kompilaci, která odkazuje pouze na sestavení, kde System.String je deklarován. Žádné obory názvů deklarované v jiných sestaveních nejsou pro tuto kompilaci známy.

Vytvoření vazby výrazu

Předchozí kód ukazuje, jak najít symbol vazbou na název. V programu jazyka C# existují další výrazy, které se dají svázat a které nejsou názvy. Abychom si to ukázali, pojďme přejít k vazbě na jednoduchý řetězcový literál.

Program "Hello World" obsahuje Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxřetězec "Hello, World!" zobrazený v konzole.

Řetězec "Hello, World!" najdete tak, že v programu vyhledáte literál s jedním řetězcem. Jakmile pak vyhledáte uzel syntaxe, získejte informace o typu pro tento uzel ze sémantického modelu. Do metody Main přidejte následující kód:

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

Struktura Microsoft.CodeAnalysis.TypeInfo obsahuje TypeInfo.Type vlastnost, která umožňuje přístup k sémantickým informacím o typu literálu. V tomto příkladu string je to typ . Přidejte deklaraci, která přiřadí tuto vlastnost k místní proměnné:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

K dokončení tohoto kurzu vytvoříme dotaz LINQ, který vytvoří sekvenci všech veřejných metod deklarovaných u string typu, který vrací string. Tento dotaz je složitý, takže ho sestavíme řádek po řádku a pak ho rekonstruujme jako jediný dotaz. Zdrojem tohoto dotazu je posloupnost všech členů deklarovaných u string typu :

var allMembers = stringTypeSymbol?.GetMembers();

Tato zdrojová sekvence obsahuje všechny členy, včetně vlastností a polí, proto ji vyfiltrujte pomocí ImmutableArray<T>.OfType metody a vyhledejte prvky, které jsou Microsoft.CodeAnalysis.IMethodSymbol objekty:

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

Dále přidejte další filtr, který vrátí pouze ty metody, které jsou veřejné a vrátí string:

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

Výběrem pouze vlastnosti name a pouze jedinečných názvů odeberete všechna přetížení:

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

Můžete také vytvořit úplný dotaz pomocí syntaxe dotazu LINQ a pak zobrazit všechny názvy metod v konzole:

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

Sestavte a spusťte program. Měl by se zobrazit následující výstup:

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

Použili jste sémantické rozhraní API k vyhledání a zobrazení informací o symbolech, které jsou součástí tohoto programu.