Partilhar via


Comece com a análise semântica

Este tutorial parte do princípio de que conheces a Syntax API. O artigo começar com a análise de sintaxe oferece uma introdução suficiente.

Neste tutorial, explora as APIs de Símbolos e Vinculação. Estas APIs fornecem informação sobre o significado semântico de um programa. Permitem-lhe colocar e responder a perguntas sobre os tipos representados por qualquer símbolo no seu programa.

Vai precisar de instalar o SDK da Plataforma do Compilador .NET:

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.

  1. Executar o instalador do Visual Studio
  2. Selecione Modificar
  3. Verifique a tarefa de desenvolvimento de extensões do Visual Studio.
  4. Abra o nó de desenvolvimento de extensões do Visual Studio na árvore de resumo.
  5. Certifique-se de que a caixa de seleção para .NET Compiler Platform SDK está assinalada.
  6. Selecione Modificar.

Opcionalmente, você também desejará que o editor DGML exiba gráficos no visualizador:

  1. Abra o nó Componentes Individuais na árvore de resumo.
  2. Marque a caixa para o editor DGML

Instalar usando o Visual Studio Installer - separador Componentes Individuais

  1. Executar o instalador do Visual Studio
  2. Selecione Modificar
  3. Selecione a guia Componentes individuais
  4. 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 .
  5. Selecione Modificar.

Opcionalmente, você também desejará que o editor DGML exiba gráficos no visualizador:

  1. Assinale a caixa do editor DGML. Vais encontrá-lo na secção de ferramentas de código .

Compreender Compilações e Símbolos

À medida que trabalhas mais com o .NET Compiler SDK, vais familiarizando-te com as distinções entre a API de Sintaxe e a API Semântica. A API de Sintaxe permite-lhe analisar a estrutura de um programa. No entanto, muitas vezes queres informação mais rica sobre a semântica ou o significado de um programa. Embora um ficheiro de código solto ou um excerto de código Visual Basic ou C# possa ser analisado sintaticamente isoladamente, não faz sentido fazer perguntas como "qual é o tipo desta variável" isoladamente. O significado de um nome de tipo pode depender de referências de assembly, importações de namespace ou outros ficheiros de código. Essas questões são respondidas usando a API Semântica, especificamente a classe Microsoft.CodeAnalysis.Compilation.

Uma instância de Compilation é análoga a um único projeto visto pelo compilador e representa tudo o que é necessário para compilar um programa em Visual Basic ou C#. A compilação inclui o conjunto de ficheiros fonte a compilar, referências de assembly e opções do compilador. Podes raciocinar sobre o significado do código usando toda a outra informação neste contexto. A Compilation permite-lhe encontrar Símbolos – entidades como tipos, namespaces, membros e variáveis a que os nomes e outras expressões se referem. O processo de associar nomes e expressões a Símboloschama-se Ligação.

Tal como Microsoft.CodeAnalysis.SyntaxTree, Compilation é uma classe abstrata com derivados específicos da linguagem. Ao criar uma instância de Compilation, deve invocar um método de fábrica na classe Microsoft.CodeAnalysis.CSharp.CSharpCompilation (ou Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Consulta de símbolos

Neste tutorial, volta a olhar para o programa "Hello World". Desta vez, consultas os símbolos no programa para perceberes que tipos esses símbolos representam. Consultas os tipos num namespace e aprendes a encontrar os métodos disponíveis num tipo.

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 "SemanticQuickStart" 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.Generic;
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();

De seguida, a partir da árvore que já criaste, constrói um CSharpCompilation. A amostra "Hello World" baseia-se nos tipos String e Console. É necessário fazer referência ao assembly que declara esses dois tipos na sua compilação. Adicione a seguinte linha ao seu método Main para criar uma compilação da sua árvore de sintaxe, incluindo a referência à assembly apropriada:

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

O CSharpCompilation.AddReferences método adiciona referências à compilação. O MetadataReference.CreateFromFile método carrega uma montagem como referência.

Consulta ao modelo semântico

Depois de ter um Compilation, pode pedir um SemanticModel para qualquer SyntaxTree contido nesse Compilation. Pode pensar no modelo semântico como a fonte de toda a informação que normalmente obteria do Intellisense. O SemanticModel pode responder a perguntas como "Que nomes estão em escopo neste local?", "Que membros são acessíveis a partir deste método?", "Que variáveis são usadas neste bloco de texto?" e "A que se refere este nome/expressão?" Adicione esta declaração para criar o modelo semântico.

SemanticModel model = compilation.GetSemanticModel(tree);

Vinculação de um nome

O Compilation cria o SemanticModel a partir do SyntaxTree. Depois de criar o modelo, pode consultá-lo para encontrar a primeira using diretiva e recuperar a informação do símbolo para o System namespace. Adicione estas duas linhas ao seu Main método para criar o modelo semântico e recuperar o símbolo da primeira using diretiva:

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

O código anterior mostra como vincular o nome na primeira diretiva using para recuperar um Microsoft.CodeAnalysis.SymbolInfo para o System espaço de nomes. O código anterior também ilustra que utilizas o modelo sintático para encontrar a estrutura do código; Usas o modelo semântico para perceber o seu significado. O modelo sintático encontra a cadeia System na using diretiva. O modelo semântico tem toda a informação sobre os tipos definidos no System namespace.

A partir do SymbolInfo objeto pode obter o Microsoft.CodeAnalysis.ISymbol usando a propriedade SymbolInfo.Symbol. Esta propriedade devolve o símbolo a que esta expressão se refere. Para expressões que não se referem a nada (como literais numéricos) esta propriedade é null. Quando o SymbolInfo.Symbol não é nulo, o ISymbol.Kind denota o tipo do símbolo. Neste exemplo, a ISymbol.Kind propriedade é uma SymbolKind.Namespace. Adicione o seguinte código ao seu método Main. Recupera o símbolo do System namespace e, em seguida, exibe todos os namespaces filhos declarados no namespace System:

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

Executa o programa e deverá ver o seguinte resultado:

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

Observação

A saída não inclui todos os namespace que são um namespace filho do System namespace. Mostra todos os namespaces presentes nesta compilação, que apenas referenciam a assembly onde System.String é declarado. Quaisquer namespaces declarados noutras assembleias não são conhecidos nesta compilação

Ligar uma expressão

O código anterior mostra como encontrar um símbolo ao associar um nome. Existem outras expressões num programa C# que podem ser atribuídas e que não são nomes. Para demonstrar esta capacidade, vamos aceder à vinculação de uma string literal simples.

O programa "Hello World" contém uma Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax, a cadeia de caracteres "Hello, World!" exibida na consola.

Encontra-se a sequência "Olá, Mundo!" localizando a única sequência literal no programa. Depois de localizares o nó sintático, obtém a informação do tipo desse nó no modelo semântico. Adicione o seguinte código ao seu Main método:

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

A Microsoft.CodeAnalysis.TypeInfo estrutura inclui uma TypeInfo.Type propriedade que permite o acesso à informação semântica sobre o tipo do literal. Neste exemplo, esse é o string tipo. Adicione uma declaração que atribua esta propriedade a uma variável local:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Para terminar este tutorial, vamos construir uma consulta LINQ que cria uma sequência de todos os métodos públicos declarados no string tipo que retornam um string. Esta consulta torna-se complexa, por isso vamos construí-la linha a linha e depois reconstruí-la como uma única consulta. A fonte para esta consulta é a sequência de todos os membros declarados no string tipo:

var allMembers = stringTypeSymbol?.GetMembers();

Essa sequência de origem contém todos os membros, incluindo propriedades e campos, por isso filtre-a usando o ImmutableArray<T>.OfType método para encontrar elementos que são Microsoft.CodeAnalysis.IMethodSymbol objetos:

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

De seguida, adicione outro filtro para devolver apenas os métodos que são públicos e que devolvam um string.

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

Selecione apenas a propriedade do nome, e apenas nomes distintos removendo quaisquer sobrecargas:

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

Também pode construir a consulta completa usando a sintaxe da consulta LINQ e depois mostrar todos os nomes dos métodos na consola:

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

Constrói e executa o programa. Deverá ver o seguinte resultado:

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

Usou a API Semântica para encontrar e mostrar informações sobre os símbolos que fazem parte deste programa.