Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Neste tutorial, você explorará a API de Sintaxe. A API de Sintaxe fornece acesso às estruturas de dados que descrevem um programa C# ou Visual Basic. Essas estruturas de dados têm detalhes suficientes de que podem representar totalmente qualquer programa de qualquer tamanho. Essas estruturas podem descrever programas completos que compilam e são executados corretamente. Eles também podem descrever programas incompletos, conforme você os escreve, no editor.
Para habilitar essa expressão rica, as estruturas de dados e as APIs que compõem a API de Sintaxe são necessariamente complexas. Vamos começar com a aparência da estrutura de dados para o programa típico "Olá, Mundo":
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Examine o texto do programa anterior. Você reconhece elementos familiares. O texto inteiro representa um único arquivo de origem ou uma unidade de compilação. As três primeiras linhas desse arquivo de origem são diretivas de uso. A origem restante está contida em uma declaração de namespace. A declaração de namespace contém uma declaração de classe. A declaração de classe contém uma declaração de método.
A API de Sintaxe cria uma estrutura de árvore com a raiz que representa a unidade de compilação. Os nós na árvore representam as diretivas, a using declaração de namespace e todos os outros elementos do programa. A estrutura da árvore continua até os níveis mais baixos: a cadeia de caracteres "Olá, Mundo!" é um token literal de cadeia de caracteres que é descendente de um argumento. A API de Sintaxe fornece acesso à estrutura do programa. Você pode consultar práticas de código específicas, percorrer toda a árvore para entender 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 usando a API de Sintaxe. A API de Sintaxe nada mais é do que uma API formal que descreve os constructos de código familiares que você conhece do C#. Os recursos completos incluem informações sobre como o código é formatado, incluindo quebras de linha, espaço em branco e recuo. Usando essas informações, você pode representar totalmente o código como escrito e lido por programadores humanos ou pelo compilador. Usar essa estrutura permite que você interaja com o código-fonte em um nível profundamente significativo. Não são mais cadeias de caracteres de texto, mas dados que representam a estrutura de um programa C#.
Para começar, você precisará instalar o SDK do .NET Compiler Platform:
Instruções de instalação – Instalador do Visual Studio
Há duas maneiras diferentes de encontrar o SDK do .NET Compiler Platform no Instalador do Visual Studio:
Instalar usando o instalador do Visual Studio – exibição de cargas de trabalho
O SDK do .NET Compiler Platform 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 carga de trabalho de desenvolvimento de extensão do Visual Studio .
- Abra o nó de desenvolvimento de extensão do Visual Studio na árvore de visão geral.
- Verifique se a caixa do SDK do .NET Compiler Platform está marcada.
- Selecione Modificar.
Opcionalmente, você também desejará que o editor DGML exiba grafos no visualizador:
- Abra o nó Componentes individuais na árvore de resumo.
- Marque a caixa do Editor DGML
Instalar usando a guia Instalador do Visual Studio – Componentes individuais
- Executar o Instalador do Visual Studio
- Selecione Modificar
- Selecione a guia Componentes individuais
- Marque a opção SDK do .NET Compiler Platform. Você o encontrará na parte superior na seção Compiladores, ferramentas de build e runtimes .
- Selecione Modificar.
Opcionalmente, você também desejará que o editor DGML exiba grafos no visualizador:
- Marque a caixa de seleção do editor DGML. Você o encontrará na seção Ferramentas de código .
Noções básicas sobre árvores de sintaxe
Use 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 os utilitários para analisar e construir árvores de sintaxe. É assim que você pesquisa o código em busca de elementos de sintaxe específicos ou lê o código de um programa.
Uma árvore de sintaxe é uma estrutura de dados usada pelos compiladores C# e Visual Basic para entender os programas C# e Visual Basic. As árvores de sintaxe são produzidas pelo mesmo analisador executado quando um projeto é criado ou um desenvolvedor atinge F5. As árvores de sintaxe têm total fidelidade com o idioma; cada bit de informação em um arquivo de código é representado na árvore. Escrever uma árvore de sintaxe no texto reproduz o texto original exato analisado. As árvores de sintaxe também são imutáveis; uma vez criada uma árvore de sintaxe nunca pode ser alterada. Os utilizadores das árvores podem analisar as árvores em vários threads, sem bloqueios ou outras medidas de concorrência, sabendo que os dados permanecem imóveis. Você pode usar APIs para criar novas árvores que são o resultado da modificação de uma árvore existente.
Os quatro principais blocos de construção de árvores de sintaxe são:
- A classe Microsoft.CodeAnalysis.SyntaxTree, cuja instância representa uma árvore de análise completa. SyntaxTree é uma classe abstrata que tem derivados específicos da linguagem. Use os métodos de análise da Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree classe (ou Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree) para analisar o texto em C# (ou Visual Basic).
- A Microsoft.CodeAnalysis.SyntaxNode classe, que representa construções sintacticas, como declarações, instruções, cláusulas e expressões.
- A Microsoft.CodeAnalysis.SyntaxToken estrutura, que representa uma palavra-chave, identificador, operador ou pontuação individual.
- Por fim, a Microsoft.CodeAnalysis.SyntaxTrivia estrutura, que representa elementos sintaticamente insignificantes, como o espaço em branco entre tokens, diretivas de pré-processamento e comentários.
Trivia, tokens e nós são compostos hierarquicamente para formar uma árvore que representa completamente tudo em um fragmento de código de Visual Basic ou de C#. Você pode ver essa estrutura usando a janela Visualizador de Sintaxe . No Visual Studio, escolha Exibir>Outras Janelas>Visualizador de Sintaxe. Por exemplo, o arquivo de origem C# anterior examinado usando o Visualizador de Sintaxe se parece com a seguinte figura:
SintaxeNode: Azul | SintaxeToken: Verde | SyntaxTrivia:
Vermelho
Ao navegar por essa estrutura de árvore, você pode encontrar qualquer instrução, expressão, token ou espaço em branco em um arquivo de código.
Embora você possa encontrar qualquer coisa em um arquivo de código usando as APIs de sintaxe, a maioria dos cenários envolve examinar pequenos trechos de código ou pesquisar instruções ou fragmentos específicos. Os dois exemplos a seguir mostram usos típicos para procurar a estrutura do código ou pesquisar instruções simples.
Percorrendo árvores
Você pode examinar os nós em uma árvore de sintaxe de duas maneiras. Você pode percorrer a árvore para examinar cada nó ou pode consultar elementos ou nós específicos.
Passagem manual
Você pode ver o código concluído para este exemplo em nosso repositório GitHub.
Observação
Os tipos de árvore de sintaxe usam herança para descrever os diferentes elementos de sintaxe válidos em locais diferentes no programa. Usar essas APIs geralmente significa converter propriedades ou membros da coleção em tipos derivados específicos. Nos exemplos a seguir, a atribuição e a conversão são instruções separadas, usando variáveis tipadas explicitamente. Você pode ler o código para ver os tipos de retorno da API e o tipo de runtime dos objetos retornados. Na prática, é mais comum usar variáveis tipadas implicitamente e contar com nomes de API para descrever o tipo de objetos que estão sendo examinados.
Crie um novo projeto de C# Ferramenta Independente de Análise de Código:
- No Visual Studio, escolha Arquivo>Novo>Projeto para exibir a caixa de diálogo Novo Projeto.
- Em Visual C#>Extensibility, escolha Ferramenta de Análise de Código Independente.
- Nomeie seu projeto como "SintaxeTreeManualTraversal" e clique em OK.
Você vai analisar o programa básico "Olá, Mundo!", mostrado anteriormente.
Adicione o texto para o programa Hello World como uma constante em sua Program classe:
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!"");
}
}
}";
Em seguida, adicione o código a seguir para criar a árvore de sintaxe para o texto de 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 você pode examinar os nós na árvore. Adicione estas linhas ao seu 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}");
Execute o aplicativo para ver o que seu código descobriu sobre o nó raiz nesta árvore.
Normalmente, você percorreria a árvore para saber mais sobre o código. Neste exemplo, você está analisando o código que sabe para explorar as APIs. Adicione o seguinte código para examinar o membro primeiro 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. Ele representa tudo no escopo da declaração namespace HelloWorld. Adicione o código a seguir para verificar quais nós são declarados dentro do namespace HelloWorld.
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared in this namespace.");
WriteLine($"The first member is a {helloWorldDeclaration.Members[0].Kind()}.");
Execute o programa para ver o que você aprendeu.
Agora que você sabe que a declaração é uma Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax, declare uma nova variável desse tipo para examinar a declaração de classe. Essa classe contém apenas um membro: o Main método. Adicione o código a seguir para localizar o método e converta-o Main em um 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 todas as informações sintacticas sobre o método. Vamos exibir o tipo de retorno do Main método, o número e os tipos dos argumentos e o texto do corpo 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 todas as informações que você 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
Além de percorrer árvores, você também pode explorar a árvore de sintaxe usando os métodos de consulta definidos em Microsoft.CodeAnalysis.SyntaxNode. Esses métodos devem ser imediatamente familiares para qualquer pessoa familiarizada com o XPath. Você pode usar esses métodos com LINQ para localizar rapidamente as coisas em uma árvore. O SyntaxNode tem métodos de consulta, como DescendantNodes, AncestorsAndSelf e ChildNodes.
Você pode usar esses métodos de consulta para encontrar o argumento para o Main método como uma alternativa para navegar na árvore. Adicione o seguinte código à parte inferior 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 usa uma expressão LINQ e o DescendantNodes método para localizar o mesmo parâmetro do exemplo anterior.
Execute o programa e você pode ver que a expressão LINQ encontrou o mesmo parâmetro que navegar manualmente na árvore.
O exemplo usa WriteLine declarações para exibir informações sobre as árvores de sintaxe à medida que são percorridas. Você também pode aprender muito mais executando o programa concluído no depurador. Você pode examinar mais das propriedades e métodos que fazem parte da árvore de sintaxe criada para o programa hello world.
Percursores de sintaxe
Geralmente, você deseja encontrar todos os nós de um tipo específico em uma árvore de sintaxe, por exemplo, cada declaração de propriedade em um arquivo. Estendendo a Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker classe e substituindo o VisitPropertyDeclaration(PropertyDeclarationSyntax) método, você processa cada declaração de propriedade em uma árvore de sintaxe sem conhecer sua estrutura com antecedência. CSharpSyntaxWalker é um tipo específico de CSharpSyntaxVisitor que recursivamente visita um nó e cada um de seus filhos.
Este exemplo implementa um CSharpSyntaxWalker que examina uma árvore de sintaxe. Ele coleta diretivas using que encontra e que não estão importando um namespace System.
Criar um novo projeto da Ferramenta de Análise de Código Autônoma C#; nomeie-o como "SyntaxWalker".
Você pode ver o código concluído para este exemplo em nosso repositório GitHub. O exemplo no GitHub contém os dois projetos descritos neste tutorial.
Como no exemplo anterior, você pode definir uma constante de cadeia de caracteres para manter o texto do programa que você vai 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 { }
}
}";
Esse texto de origem contém using diretivas espalhadas por quatro locais diferentes: o nível do arquivo, no namespace de nível superior e nos dois namespaces aninhados. Este exemplo realça um cenário principal para usar a CSharpSyntaxWalker classe para consultar o código. Seria complicado visitar cada nó na árvore de sintaxe raiz para encontrar usando declarações. Em vez disso, você cria uma classe derivada e substitui o método que é chamado somente quando o nó atual na árvore é uma using diretiva. O visitante não trabalha em nenhum outro tipo de nó. Esse único método examina cada uma das using diretivas e cria uma coleção de namespaces que não estão no System namespace. Você cria um CSharpSyntaxWalker que examina todas as using diretivas, mas apenas as using diretivas.
Agora que você definiu o texto do programa, você precisa criar uma SyntaxTree e obter a raiz dessa árvore:
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
Em seguida, crie uma nova classe. No Visual Studio, escolha Project>Add New Item. Na caixa de diálogo Adicionar Novo Item, digite UsingCollector.cs como o nome do arquivo.
Você implementa a using funcionalidade de visitante na UsingCollector classe. Comece fazendo a classe UsingCollector derivar de CSharpSyntaxWalker.
class UsingCollector : CSharpSyntaxWalker
Você precisa de armazenamento para armazenar os nós de namespace que você está coletando. Declare uma propriedade somente leitura pública na classe UsingCollector; use essa variável para armazenar os nós UsingDirectiveSyntax encontrados:
public ICollection<UsingDirectiveSyntax> Usings { get; } = new List<UsingDirectiveSyntax>();
A classe base CSharpSyntaxWalker implementa a lógica para visitar cada nó na árvore de sintaxe. A classe derivada substitui os métodos chamados para os nós específicos nos quais você está interessado. Neste caso, você está interessado em qualquer diretiva using. Isso significa que você deve substituir o método VisitUsingDirective(UsingDirectiveSyntax). O único argumento para esse método é um Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax objeto. Essa é uma vantagem importante para o uso dos visitantes: eles chamam os métodos substituídos com argumentos já convertidos no tipo de nó específico. A Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax classe tem uma Name propriedade que armazena o nome do namespace que está sendo importado. É um Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Adicione o seguinte código na sobrescrição VisitUsingDirective(UsingDirectiveSyntax) :
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);
}
}
Assim como no exemplo anterior, você adicionou uma variedade de WriteLine instruções para ajudar a entender esse método. Você pode ver quando ele é chamado e quais argumentos são passados para ele todas as vezes.
Por fim, você precisa adicionar duas linhas de código para criar o UsingCollector e fazer com que ele visite o nó raiz, coletando todas as diretivas using. Em seguida, adicione um foreach loop para exibir 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);
}
Compile e execute o programa. Você deve 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! Você usou a API de Sintaxe para localizar tipos específicos de diretivas e declarações no código-fonte C#.