Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V tomto kurzu prozkoumáte rozhraní API syntaxe. Rozhraní API syntaxe poskytuje přístup k datovým strukturám, které popisují program jazyka C# nebo Visual Basic. Tyto datové struktury mají dostatek podrobností, aby mohly plně reprezentovat libovolný program libovolné velikosti. Tyto struktury mohou popisovat kompletní programy, které kompilují a spouští správně. Můžou také popsat neúplné programy při psaní v editoru.
Aby bylo možné tento bohatý výraz umožnit, jsou datové struktury a API, které tvoří Syntax API, nutně složité. Začněme tím, jak vypadá datová struktura pro typický program "Hello World":
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Podívejte se na text předchozího programu. Rozpoznáváte známé prvky. Celý text představuje jeden zdrojový soubor nebo jednotku kompilace. První tři řádky tohoto zdrojového souboru používají direktivy. Zbývající zdroj je obsažen v deklaraci oboru názvů. Deklarace oboru názvů obsahuje deklaraci podřízené třídy. Deklarace třídy obsahuje jednu deklaraci metody.
Syntaktické API vytvoří stromovou strukturu s kořenem představujícím jednotku kompilace. Uzly ve stromu představují direktivy using, deklaraci oboru názvů a všechny ostatní prvky programu. Stromová struktura pokračuje dolů na nejnižší úrovně: řetězec "Hello World!" je řetězcový literál token, který je potomek argumentu. Rozhraní API syntaxe poskytuje přístup ke struktuře programu. Můžete se dotazovat na konkrétní postupy kódu, projít celý strom, abyste pochopili kód, a vytvořit nové stromy úpravou existujícího stromu.
Tento stručný popis poskytuje přehled o druhu informací, které jsou přístupné pomocí rozhraní API syntaxe. Rozhraní API syntaxe není nic víc než formální rozhraní API, které popisuje známé konstrukce kódu, které znáte z jazyka C#. Mezi úplné možnosti patří informace o tom, jak je kód formátován, včetně konců řádků, prázdných znaků a odsazení. Pomocí těchto informací můžete kód plně znázorňovat jako napsaný a přečtený lidmi nebo kompilátorem. Použití této struktury umožňuje interakci se zdrojovým kódem na velmi smysluplné úrovni. Už nejsou textové řetězce, ale data, která představují strukturu programu v jazyce C#.
Abyste mohli začít, budete muset nainstalovat sadu .NET Compiler Platform SDK:
Pokyny k instalaci – Instalační program sady Visual Studio
Sada .NET Compiler Platform SDK v instalačním programu sady Visual Studio se dá najít dvěma různými způsoby:
Instalace pomocí instalačního programu sady Visual Studio – zobrazení úloh
Sada .NET Compiler Platform SDK není automaticky vybrána jako součást sady funkcí pro vývoj rozšíření sady Visual Studio. Musíte ho vybrat jako volitelnou komponentu.
- Spuštění instalačního programu sady Visual Studio
- Vyberte Upravit.
- Zkontrolujte pracovní zátěž vývoje rozšíření pro Visual Studio.
- Otevřete uzel vývoje rozšíření sady Visual Studio v přehledovém stromu.
- Ujistěte se, že políčko pro sadu .NET Compiler Platform SDK je zaškrtnuté.
- Vyberte Upravit.
Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:
- Otevřete uzel Jednotlivé komponenty v přehledovém stromu.
- Zaškrtněte políčko pro editor DGML.
Instalace pomocí instalačního programu sady Visual Studio – karta Jednotlivé komponenty
- Spuštění instalačního programu sady Visual Studio
- Vyberte Upravit.
- Vyberte kartu Jednotlivé součásti
- Zaškrtněte to okénko pro sadu .NET Compiler Platform SDK. Najdete ho v horní části v části Kompilátory, nástroje sestavení a moduly runtime .
- Vyberte Upravit.
Volitelně můžete také chtít, aby editor DGML zobrazoval grafy ve vizualizéru:
- Zaškrtněte políčko pro editor DGML. Najdete ho v části Nástroje kódu .
Principy stromů syntaxe
Rozhraní API syntaxe slouží k jakékoli analýze struktury kódu jazyka C#. Rozhraní API syntaxe zveřejňuje analyzátory, stromy syntaxe a nástroje pro analýzu a vytváření stromů syntaxe. Je to způsob, jakým hledáte kód pro konkrétní prvky syntaxe nebo čtete kód programu.
Strom syntaxe je datová struktura používaná kompilátory jazyka C# a Visual Basic k pochopení programů jazyka C# a Visual Basic. Stromy syntaxe jsou vytvářeny stejným analyzátorem, který se spouští při sestavení projektu nebo když vývojář stiskne F5. Stromy syntaxe mají plnou věrnost jazyka; Každý bit informací v souboru kódu je reprezentován ve stromu. Zápis syntaktického stromu do textu reprodukuje původní přesný text, který byl parsován. Stromy syntaxe jsou také neměnné; po vytvoření stromu syntaxe nelze nikdy změnit. Spotřebitelé stromů mohou analyzovat stromy na více vláknech bez zámků nebo jiných měr souběžnosti, protože data se nikdy nemění. Pomocí rozhraní API můžete vytvořit nové stromy, které jsou výsledkem úpravy existujícího stromu.
Čtyři hlavní stavební bloky stromů syntaxe jsou:
- Třída Microsoft.CodeAnalysis.SyntaxTree , instance, která představuje celý strom analýzy. SyntaxTree je abstraktní třída, která má deriváty specifické pro jazyk. Parsování metod Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree třídy (nebo Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree) slouží k analýze textu v jazyce C# (nebo Visual Basic).
- Třída Microsoft.CodeAnalysis.SyntaxNode , instance, které představují syntaktické konstrukty, jako jsou deklarace, příkazy, klauzule a výrazy.
- Struktura Microsoft.CodeAnalysis.SyntaxToken , která představuje jednotlivá klíčová slova, identifikátor, operátor nebo interpunkci.
- A nakonec Microsoft.CodeAnalysis.SyntaxTrivia struktura, která představuje syntakticky nevýznamné části informací, jako jsou prázdné znaky mezi tokeny, direktivy předběžného zpracování a komentáře.
Trivia, tokeny a uzly se skládají hierarchicky tak, aby vytvořily strom, který zcela představuje vše v fragmentu kódu jazyka Visual Basic nebo C#. Tuto strukturu můžete zobrazit pomocí okna Vizualizéru syntaxe . V sadě Visual Studio zvolte Zobrazit>Další okna>Vizualizér syntaxe. Například předchozí zdrojový soubor jazyka C# prozkoumáný pomocí vizualizéru syntaxe vypadá jako na následujícím obrázku:
SyntaxNode: Modrá | SyntaxToken: Zelený | SyntaxTrivia: Červená 
Když přejdete na tuto stromovou strukturu, najdete v souboru kódu libovolný příkaz, výraz, token nebo bit prázdného místa.
I když můžete najít cokoli v souboru kódu pomocí rozhraní API syntaxe, většina scénářů zahrnuje zkoumání malých fragmentů kódu nebo vyhledávání konkrétních příkazů nebo fragmentů. Dva následující příklady ukazují typické použití k procházení struktury kódu nebo hledání jednoduchých příkazů.
Procházení stromů
Uzly ve stromu syntaxe můžete prozkoumat dvěma způsoby. Strom můžete procházet, abyste prozkoumali jednotlivé uzly, nebo můžete zadat dotaz na konkrétní prvky nebo uzly.
Ruční procházení
Hotový kód této ukázky si můžete prohlédnout 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í explicitně typované proměnné. Kód si můžete přečíst, abyste viděli návratové typy rozhraní API a typ běhového prostředí 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 objektů, které se prověřují.
Vytvořte nový projekt C# samostatného nástroje pro analýzu kódu:
- V sadě Visual Studio zvolte Soubor>nový>projekt , aby se zobrazilo dialogové okno Nový projekt.
- V části Visual C#>Rozšiřitelnost zvolte Samostatný nástroj pro analýzu kódu.
- Pojmenujte projekt SyntaxTreeManualTraversal a klikněte na OK.
Budete analyzovat základní program "Hello World!" zobrazený dříve.
Přidejte text pro program Hello World jako konstantu ve třídě Program :
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!"");
}
}
}";
Dále přidejte následující kód, který vytvoří strom syntaxe pro text kódu v konstantě programText . Do metody přidejte následující řádek Main :
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Tyto dva řádky vytvoří strom a načtou kořenový uzel tohoto stromu. Teď můžete prozkoumat uzly ve stromu. Přidejte do Main své metody tyto řádky, abyste zobrazili některé vlastnosti kořenového uzlu ve stromu:
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}");
Spusťte aplikaci a zjistěte, co váš kód zjistil o kořenovém uzlu v tomto stromu.
Obvykle byste procházeli stromem, abyste se dozvěděli o kódu. V tomto příkladu analyzujete kód, který znáte k prozkoumání rozhraní API. Přidejte následující kód pro prozkoumání prvního člena root uzlu:
MemberDeclarationSyntax firstMember = root.Members[0];
WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;
Tento člen je Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax. Představuje vše v rozsahu namespace HelloWorld deklarace. Přidejte následující kód, který prozkoumá, které uzly jsou deklarovány uvnitř HelloWorld oboru názvů:
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");
Spuštěním programu zjistíte, co jste se naučili.
Teď, když víte, že deklarace je Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, deklarujte novou proměnnou tohoto typu pro prozkoumání deklarace třídy. Tato třída obsahuje pouze jeden člen: metodu Main . Přidejte následující kód, který najde metodu Main a přetypuje ji na .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];
Uzel deklarace metody obsahuje všechny syntaktické informace o metodě. Pojďme zobrazit návratový Main typ metody, počet a typy argumentů a základní text metody. Přidejte následující kód:
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];
Spuštěním programu zobrazíte všechny informace, které jste o tomto programu zjistili:
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!");
}
Metody dotazů
Kromě procházení stromů můžete také prozkoumat strom syntaxe pomocí metod dotazu definovaných na Microsoft.CodeAnalysis.SyntaxNode. Tyto metody by měly být okamžitě známé každému, kdo je obeznámen s XPathem. Pomocí LINQ můžete tyto metody použít k rychlému hledání věcí ve stromu. Obsahuje metody dotazu, jako DescendantNodes, AncestorsAndSelf a ChildNodes.
Pomocí těchto metod dotazu můžete najít argument metody Main jako alternativu k navigaci ve stromu. Do dolní části metody Main přidejte následující kód:
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);
První příkaz používá výraz LINQ a metodu DescendantNodes k vyhledání stejného parametru jako v předchozím příkladu.
Spusťte program a vidíte, že výraz LINQ našel stejný parametr jako ruční navigace ve stromu.
Ukázka používá příkazy WriteLine ke zobrazení informací o syntaktických stromech během jejich procházení. Další znalosti můžete získat spuštěním dokončeného programu pomocí ladicího programu. Můžete prozkoumat více vlastností a metod, které jsou součástí stromu syntaxe vytvořeného pro program Hello World.
Syntaxe chodců
Často chcete najít všechny uzly určitého typu ve stromu syntaxe, například každou deklaraci vlastnosti v souboru. Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker Rozšířením třídy a přepsáním VisitPropertyDeclaration(PropertyDeclarationSyntax) metody zpracujete každou deklaraci vlastnosti ve stromu syntaxe, aniž byste předem věděli její strukturu. CSharpSyntaxWalker je určitý druh CSharpSyntaxVisitor, který rekurzivně navštíví uzel a každý z jeho potomků.
Tento příklad implementuje CSharpSyntaxWalker prozkoumávající syntaktický strom. Shromažďuje direktivy using, které neimportují System obor názvů.
Vytvořte nový projekt nástroje pro analýzu kódu Stand-Alone jazyka C# a pojmenujte ho "SyntaxWalker".
Hotový kód této ukázky si můžete prohlédnout v našem úložišti GitHub. Ukázka na GitHubu obsahuje oba projekty popsané v tomto kurzu.
Stejně jako v předchozí ukázce můžete definovat řetězcovou konstantu, která bude obsahovat text programu, který budete analyzovat:
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 { }
}
}";
Tento zdrojový text obsahuje using direktivy rozptýlené napříč čtyřmi různými umístěními: na úrovni souboru, v oboru názvů nejvyšší úrovně a ve dvou vnořených oborech názvů. Tento příklad zvýrazňuje základní scénář použití CSharpSyntaxWalker třídy k dotazování kódu. Bylo by těžkopádné navštívit každý uzel v kořenovém stromu syntaxe a najít pomocí deklarací. Místo toho vytvoříte odvozenou třídu a překryjete metodu, která se volá pouze v případě, že je aktuální uzel ve stromu direktiva using. Návštěvník neprovádí žádnou práci na jiných typech uzlů. Tato jedna metoda prozkoumá každou direktivu using a vytvoří kolekci oborů názvů, které nejsou v System oboru názvů. Vytvoříte sestavu CSharpSyntaxWalker , která prozkoumá všechny using direktivy, ale pouze using direktivy.
Teď, když jste definovali text programu, musíte vytvořit SyntaxTree a získat kořen tohoto stromu:
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Dále vytvořte novou třídu. V sadě Visual Studio zvoltePřidat novou položku>. V dialogovém okně Přidat novou položku zadejte UsingCollector.cs jako název souboru.
Funkce návštěvníka using implementujete ve UsingCollector třídě. Začněte tím, že UsingCollector třídu odvozíte z CSharpSyntaxWalker.
class UsingCollector : CSharpSyntaxWalker
Potřebujete paměťové úložiště pro uzly oboru názvů, které shromažďujete. Deklarujte veřejnou vlastnost jen pro čtení ve UsingCollector třídě. Tuto proměnnou použijete k uložení UsingDirectiveSyntax nalezených uzlů:
public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();
Základní třída CSharpSyntaxWalker implementuje logiku pro návštěvu každého uzlu ve stromu syntaxe. Odvozená třída překryje metody volané pro konkrétní uzly, které jsou pro vás důležité. V tomto případě máte zájem o jakoukoli using směrnici. To znamená, že musíte přepsat metodu VisitUsingDirective(UsingDirectiveSyntax) . Jedním z argumentů této metody je Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objekt. To je důležitá výhoda použití Visitoru: volají přepsané metody s argumenty, které jsou již přetypovány na konkrétní typ uzlu. Třída Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax má Name vlastnost, která ukládá název importovaného oboru názvů. Je to Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Do přepsání VisitUsingDirective(UsingDirectiveSyntax) přidejte následující kód:
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);
}
}
Stejně jako v předchozím příkladu jste přidali různé WriteLine příkazy, které vám pomůžou pochopit tuto metodu. Uvidíte, kdy je volána a jaké argumenty se jí pokaždé předají.
Nakonec musíte přidat dva řádky kódu, abyste vytvořili UsingCollector a nechali ho projít kořenovým uzlem, kde shromáždí všechny direktivy using. Pak přidejte smyčku foreach , která zobrazí všechny direktivy using , které kolektor našel:
var collector = new UsingCollector();
collector.Visit(root);
foreach (var directive in collector.Usings)
{
WriteLine(directive.Name);
}
Zkompilujte a spusťte program. Měl by se zobrazit následující výstup:
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 . . .
Gratulujeme! Rozhraní API syntaxe jste použili k vyhledání konkrétních typů direktiv a deklarací ve zdrojovém kódu jazyka C#.