Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Neste tutorial, vais explorar a API de Sintaxe. A API Syntax fornece acesso às estruturas de dados que descrevem um programa em C# ou Visual Basic. Estas estruturas de dados têm detalhe suficiente para poderem representar completamente qualquer programa de qualquer tamanho. Estas estruturas podem descrever programas completos que compilam e executam corretamente. Também podem descrever programas incompletos, à medida que os escreves, no editor.
Para permitir esta expressão enriquecedora, as estruturas de dados e APIs que compõem a API de sintaxe são necessariamente complexas. Comecemos por como é a estrutura de dados do típico programa "Hello World":
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Veja o texto do programa anterior. Reconheces elementos familiares. O texto inteiro representa um único ficheiro fonte, ou uma unidade de compilação. As primeiras três linhas desse ficheiro fonte usam diretivas. A fonte restante está contida numa declaração de espaço de nomes. A declaração do namespace contém uma declaração de classe filha. A declaração de classe contém uma declaração de método.
A API Syntax cria uma estrutura em árvore cuja raiz representa a unidade de compilação. Os nós na árvore representam as using diretivas, a declaração do namespace e todos os outros elementos do programa. A estrutura em árvore continua até aos níveis mais baixos: a cadeia "Hello World!" é um token literal de cadeia que é descendente de um argumento. A API Syntax fornece acesso à estrutura do programa. Pode consultar práticas específicas de código, percorrer toda a árvore para compreender o código e criar novas árvores modificando a árvore existente.
Essa breve descrição fornece uma visão geral do tipo de informação acessível através da API Syntax. A API de Sintaxe não é mais do que uma API formal que descreve os construtos de código familiares que conheces em C#. As capacidades completas incluem informações sobre a formatação do código, incluindo quebras de linha, espaços em branco e recuos. Com esta informação, pode representar completamente o código tal como escrito e lido por programadores humanos ou pelo compilador. Usar esta estrutura permite-lhe interagir com o código-fonte a um nível profundamente significativo. Já não são cadeias de texto, mas sim dados que representam a estrutura de um programa em C#.
Para começar, você precisará instalar o .NET Compiler Platform SDK:
Instruções de instalação - Visual Studio Installer
Há duas maneiras diferentes de localizar o SDK da plataforma de compilador .NET no instalador do Visual Studio:
Instalar usando o Visual Studio Installer - Vista de Workloads
O SDK da plataforma de compilador .NET não é selecionado automaticamente como parte da carga de trabalho de desenvolvimento de extensão do Visual Studio. Você deve selecioná-lo como um componente opcional.
- Executar o instalador do Visual Studio
- Selecione Modificar
- Verifique a tarefa de desenvolvimento de extensões do Visual Studio.
- Abra o nó de desenvolvimento de extensões do Visual Studio na árvore de resumo.
- Certifique-se de que a caixa de seleção para .NET Compiler Platform SDK está assinalada.
- Selecione Modificar.
Opcionalmente, você também desejará que o editor DGML exiba gráficos no visualizador:
- Abra o nó Componentes Individuais na árvore de resumo.
- Marque a caixa para o editor DGML
Instalar usando o Visual Studio Installer - separador Componentes Individuais
- Executar o instalador do Visual Studio
- Selecione Modificar
- Selecione a guia Componentes individuais
- Marque a caixa de seleção para .NET Compiler Platform SDK. Você o encontrará na parte superior na seção Compiladores, ferramentas de compilação e tempos de execução .
- Selecione Modificar.
Opcionalmente, você também desejará que o editor DGML exiba gráficos no visualizador:
- Assinale a caixa do editor DGML. Vais encontrá-lo na secção de ferramentas de código .
Compreensão das árvores sintáticas
Utiliza-se a API de sintaxe para qualquer análise da estrutura do código C#. A API de sintaxe expõe os analisadores, as árvores de sintaxe e as utilidades para analisar e construir árvores sintácicas. É como procuras código por elementos específicos da sintaxe ou lês o código de um programa.
Uma árvore sintáctica é uma estrutura de dados usada pelos compiladores C# e Visual Basic para compreender programas C# e Visual Basic. As árvores de sintaxe são produzidas pelo mesmo parser que é executado quando um projeto é construído ou quando um programador atinge F5. As árvores sintáticas têm fidelidade total com a linguagem; cada bit de informação num ficheiro de código é representado na árvore. Escrever uma árvore sintática para texto reproduz exatamente o texto original que foi analisado. As árvores sintácticas também são imutáveis; uma vez criada, uma árvore sintáctica nunca pode ser alterada. Os utilizadores das árvores podem analisá-las em múltiplos threads, sem bloqueios ou outras medidas de concorrência, sabendo que os dados nunca mudam. Pode usar APIs para criar novas árvores que resultam da modificação de uma árvore existente.
Os quatro principais blocos de construção das árvores de sintaxe são:
- A classe Microsoft.CodeAnalysis.SyntaxTree, uma instância que representa uma árvore de sintaxe completa. SyntaxTree é uma classe abstrata que possui derivadas específicas da linguagem. Usas os métodos de análise da Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree (ou Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree) classe para analisar texto em C# (ou Visual Basic).
- A Microsoft.CodeAnalysis.SyntaxNode classe cujas instâncias representam construções sintáticas como declarações, afirmações, cláusulas e expressões.
- A Microsoft.CodeAnalysis.SyntaxToken estrutura, que representa uma palavra-chave individual, identificador, operador ou pontuação.
- E, por fim, a Microsoft.CodeAnalysis.SyntaxTrivia estrutura, que representa bits sintaticamente insignificantes de informação, como o espaço em branco entre tokens, diretivas de pré-processamento e comentários.
Trivia, tokens e nós são organizados hierarquicamente para formar uma árvore que representa integralmente tudo num fragmento de código de Visual Basic ou C#. Pode ver esta estrutura usando a janela do Syntax Visualizer . No Visual Studio, escolha Ver>Outro Visualizador de Sintaxe>. Por exemplo, o ficheiro-fonte C# anterior, examinado usando o Syntax Visualizer, assemelha-se à figura seguinte:
SyntaxNode: Blue | SyntaxToken: Green | SyntaxTrivia:
Vermelho
Ao navegar por esta estrutura de árvore, pode encontrar qualquer instrução, expressão, token ou espaço em branco num ficheiro de código.
Embora possa encontrar tudo num ficheiro de código usando as APIs de sintaxe, a maioria dos cenários envolve examinar pequenos excertos de código ou procurar instruções ou fragmentos específicos. Os dois exemplos que se seguem mostram usos típicos para navegar pela estrutura do código ou procurar instruções únicas.
Árvores atravessadoras
Pode examinar os nós numa árvore sintáctica de duas formas. Pode percorrer a árvore para examinar cada nó ou fazer consultas para elementos ou nós específicos.
Percurso manual
Pode ver o código finalizado deste exemplo no nosso repositório GitHub.
Observação
Os tipos de Árvore de Sintaxe usam herança para descrever os diferentes elementos sintaxiques válidos em diferentes locais do programa. Usar estas APIs frequentemente significa converter propriedades ou membros de coleção em tipos derivados específicos. Nos exemplos seguintes, a atribuição e as conversões são instruções separadas, usando variáveis tipadas explicitamente. Pode ler o código para ver os tipos de retorno da API e o tipo de execução dos objetos devolvidos. Na prática, é mais comum usar variáveis tipadas implicitamente e confiar em nomes de APIs para descrever o tipo de objetos examinados.
Crie um novo projeto Ferramenta Stand-Alone de Análise de Código C# :
- No Visual Studio, escolha Fichar>Novo>Projeto para mostrar o diálogo Novo Projeto.
- Em Visual C#>Extensibilidade, escolha Ferramenta de Análise de Código Autônoma.
- Nomeie o seu projeto como "SyntaxTreeManualTraversal" e clique em OK.
Vais analisar o programa básico "Hello World!" mostrado anteriormente.
Adicione o texto do programa Hello World como constante na sua Program aula:
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!"");
}
}
}";
De seguida, adicione o seguinte código para construir a árvore sintáctica do texto do código na programText constante. Adicione a seguinte linha ao seu Main método:
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Essas duas linhas criam a árvore e recuperam o nó raiz dessa árvore. Agora pode examinar os nós na árvore. Adicione estas linhas ao método Main para exibir algumas das propriedades do nó raiz na árvore:
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}");
Executa a aplicação para ver o que o teu código descobriu sobre o nó raiz nesta árvore.
Normalmente, atravessarias a árvore para aprender sobre o código. Neste exemplo, estás a analisar código que conheces para explorar as APIs. Adicione o seguinte código para examinar o primeiro elemento do nó root:
MemberDeclarationSyntax firstMember = root.Members[0];
WriteLine($"The first member is a {firstMember.Kind()}.");
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;
Esse membro é um Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax. Representa tudo o que está no âmbito da namespace HelloWorld declaração. Adicione o seguinte código para examinar quais nós são declarados dentro do espaço de nomes HelloWorld.
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");
Executa o programa para ver o que aprendeste.
Agora que sabes que a declaração é um Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, declara uma nova variável desse tipo para examinar a declaração da classe. Esta classe contém apenas um membro: o Main método. Adicione o código seguinte para encontrar o método Main e converta-o para 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];
O nó de declaração do método contém toda a informação sintática sobre o método. Vamos mostrar o tipo de retorno do Main método, o número e os tipos dos argumentos, e o corpo do texto do método. Adicione o seguinte código:
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];
Execute o programa para ver toda a informação que descobriu sobre este programa:
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!");
}
Métodos de consulta
Para além de atravessar árvores, pode também explorar a árvore sintática usando os métodos de consulta definidos em Microsoft.CodeAnalysis.SyntaxNode. Estes métodos devem ser imediatamente familiares para qualquer pessoa familiarizada com XPath. Podes usar estes métodos com o LINQ para encontrar rapidamente coisas numa árvore. Tem SyntaxNode métodos de consulta como DescendantNodes, AncestorsAndSelf e ChildNodes.
Pode usar estes métodos de consulta para encontrar o argumento do Main método como alternativa à navegação da árvore. Adicione o seguinte código no final do seu Main método:
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);
A primeira instrução utiliza uma expressão LINQ e o DescendantNodes método para localizar o mesmo parâmetro do exemplo anterior.
Execute o programa e pode ver que a expressão LINQ encontrou o mesmo parâmetro que ao navegar manualmente na árvore.
O exemplo utiliza WriteLine instruções para mostrar informação sobre as árvores sintáticas à medida que são percorridas. Também pode aprender muito mais ao executar o programa finalizado no depurador. Pode analisar mais propriedades e métodos que fazem parte da árvore sintática criada para o programa hello world.
Analisadores de sintaxe
Muitas vezes queres encontrar todos os nós de um tipo específico numa árvore de sintaxe, por exemplo, cada declaração de propriedade num ficheiro. Ao estender a Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker classe e sobrepor o VisitPropertyDeclaration(PropertyDeclarationSyntax) método, processa cada declaração de propriedade numa árvore sintáctica sem conhecer previamente a sua estrutura. CSharpSyntaxWalker é um tipo específico de CSharpSyntaxVisitor que visita recursivamente um nó e cada um dos seus filhos.
Este exemplo implementa um CSharpSyntaxWalker que examina uma árvore sintática. Recolhe as using diretivas que encontra e que não estão a importar um System namespace.
Criar um novo projeto C# de Ferramenta Stand-Alone de Análise de Código; chame-lhe "SyntaxWalker."
Pode ver o código finalizado deste exemplo no nosso repositório GitHub. O exemplo no GitHub contém ambos os projetos descritos neste tutorial.
Tal como no exemplo anterior, podes definir uma constante de string para conter o texto do programa que vais analisar:
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 { }
}
}";
Este texto fonte contém using diretivas espalhadas por quatro locais diferentes: ao nível do ficheiro, no espaço de nomes de nível superior, e nos dois namespaces aninhados. Este exemplo destaca um cenário central para usar a CSharpSyntaxWalker classe para consultar código. Seria complicado visitar todos os nós da árvore de sintaxe raiz para encontrar declarações de uso. Em vez disso, cria-se uma classe derivada e sobrepõe-se ao método que só é chamado quando o nó atual na árvore é uma using diretiva. O seu visitante não efetua qualquer trabalho em outros tipos de nós. Este único método examina cada uma das using diretivas e constrói uma coleção dos namespaces que não estão no System namespace. Constróis uma CSharpSyntaxWalker que examina todas as using diretivas, mas apenas as using diretivas.
Agora que definiste o texto do programa, precisas de criar um SyntaxTree e obter a raiz dessa árvore:
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
De seguida, cria uma nova turma. No Visual Studio, escolhe Adicionar Novo Item do Projeto>. Na janela de diálogo Adicionar Novo Item , escreve UsingCollector.cs como nome do ficheiro.
Implementa a funcionalidade de using visitante na classe UsingCollector. Comece por fazer com que a UsingCollector classe derive de CSharpSyntaxWalker.
class UsingCollector : CSharpSyntaxWalker
Precisas de armazenamento para armazenar os nós de namespace que estás a recolher. Declare uma propriedade pública de apenas leitura na UsingCollector classe; usa esta variável para armazenar os UsingDirectiveSyntax nós que encontrar:
public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();
A classe base CSharpSyntaxWalker implementa a lógica para visitar cada nó na árvore sintática. A classe derivada substitui os métodos associados aos nós específicos de que necessitas. Neste caso, está interessado em qualquer diretiva using. Isso significa que tens de sobrescrever o método VisitUsingDirective(UsingDirectiveSyntax). O único argumento a favor deste método é um Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objeto. Essa é uma vantagem importante de usar os visitantes: eles chamam os métodos sobrepostos com argumentos já lançados para o tipo específico de nó. A Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax classe tem uma Name propriedade que armazena o nome do namespace que está a ser importado. É um Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Adicione o seguinte código no VisitUsingDirective(UsingDirectiveSyntax) override:
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);
}
}
Tal como no exemplo anterior, adicionou uma variedade de WriteLine afirmações para ajudar a compreender este método. Consegues ver quando é chamado e que argumentos lhe são transmitidos de cada vez.
Finalmente, precisas de adicionar duas linhas de código para criar o UsingCollector e fazer com que ele visite o nó raiz, recolhendo todas as using diretivas. Depois, adicione um foreach loop para mostrar todas as using diretivas que o seu coletor encontrou:
var collector = new UsingCollector();
collector.Visit(root);
foreach (var directive in collector.Usings)
{
WriteLine(directive.Name);
}
Compila e executa o programa. Deverá ver o seguinte resultado:
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 . . .
Parabéns! Usou a API Syntax para localizar tipos específicos de diretivas e declarações no código-fonte C#.