Compartilhar via


Introdução à análise semântica

Este tutorial pressupõe que você esteja familiarizado com a API de Sintaxe. O artigo introdução à análise de sintaxe fornece introdução suficiente.

Neste tutorial, você explorará as APIs de Símbolo e Binding. Essas APIs fornecem informações sobre o significado semântico de um programa. Eles permitem que você faça e responda perguntas sobre os tipos representados por qualquer símbolo em seu programa.

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.

  1. Executar o Instalador do Visual Studio
  2. Selecione Modificar
  3. Verifique a carga de trabalho de desenvolvimento de extensão do Visual Studio .
  4. Abra o nó de desenvolvimento de extensão do Visual Studio na árvore de visão geral.
  5. Verifique se a caixa do SDK do .NET Compiler Platform está marcada.
  6. Selecione Modificar.

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

  1. Abra o nó Componentes individuais na árvore de resumo.
  2. Marque a caixa do Editor DGML

Instalar usando a guia Instalador do Visual Studio – Componentes individuais

  1. Executar o Instalador do Visual Studio
  2. Selecione Modificar
  3. Selecione a guia Componentes individuais
  4. Marque a opção SDK do .NET Compiler Platform. Você o encontrará na parte superior na seção Compiladores, ferramentas de build e runtimes .
  5. Selecione Modificar.

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

  1. Marque a caixa de seleção do editor DGML. Você o encontrará na seção Ferramentas de código .

Noções básicas sobre compilações e símbolos

À medida que você trabalha mais com o SDK do Compilador .NET, você se familiariza com as distinções entre a API de Sintaxe e a API Semântica. A API de Sintaxe permite que você examine a estrutura de um programa. No entanto, muitas vezes você deseja obter informações mais avançadas sobre a semântica ou o significado de um programa. Embora um arquivo de código solto ou um trecho de código em Visual Basic ou C# possa ser analisado sintaticamente de maneira isolada, não é significativo perguntar "qual é o tipo dessa variável" sem contexto. O significado de um nome de tipo pode depender de referências de assembly, importações de namespace ou outros arquivos de código. Essas perguntas são respondidas usando a API Semântica, especificamente a Microsoft.CodeAnalysis.Compilation classe.

Uma instância de Compilation é análoga a um único projeto, conforme visto pelo compilador e representa tudo o que é necessário para compilar um programa do Visual Basic ou C#. A compilação inclui o conjunto de arquivos de origem a serem compilados, referências de assembly e opções do compilador. Você pode raciocinar sobre o significado do código usando todas as outras informações neste contexto. Um Compilation permite que você encontre Símbolos – entidades como tipos, namespaces, membros e variáveis às quais os nomes e outras expressões se referem. O processo de associar nomes e expressões com símbolos é chamado Ligação.

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

Consultando símbolos

Neste tutorial, você verá o programa "Olá, Mundo" novamente. Desta vez, você consulta os símbolos no programa para entender quais tipos esses símbolos representam. Você consulta os tipos em um namespace e aprende a encontrar os métodos disponíveis em um tipo.

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

Em seguida, crie um CSharpCompilation a partir da árvore que você já criou. O exemplo "Olá, Mundo" depende dos tipos String e Console. Você precisa referenciar o assembly que declara esses dois tipos em sua compilação. Adicione a seguinte linha ao método Main para criar uma compilação da árvore de sintaxe, incluindo a referência ao assembly apropriado:

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 um assembly como referência.

Consultando o modelo semântico

Uma vez que você tem um Compilation, você pode solicitar um SemanticModel para qualquer SyntaxTree contido nesse Compilation. Você pode pensar no modelo semântico como a origem de todas as informações que normalmente obteria do intellisense. A SemanticModel pode responder perguntas como "Quais nomes estão no escopo neste local?", "Quais membros são acessíveis desse método?", "Quais variáveis são usadas neste bloco de texto?" e "O que esse nome/expressão se refere?" Adicione esta instrução para criar o modelo semântico:

SemanticModel model = compilation.GetSemanticModel(tree);

Associar um nome

O Compilation cria a SemanticModel a partir do SyntaxTree. Depois de criar o modelo, você pode consultá-lo para localizar a primeira using diretiva e recuperar as informações de símbolo para o System namespace. Adicione estas duas linhas ao método Main 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 associar o nome na primeira diretiva using a fim de recuperar um Microsoft.CodeAnalysis.SymbolInfo para o espaço de nomes System. O código anterior também ilustra que você usa o modelo de sintaxe para localizar a estrutura do código; você usa o modelo semântico para entender seu significado. O modelo de sintaxe localiza a cadeia de caracteres System na using diretiva. O modelo semântico tem todas as informações sobre os tipos definidos no System namespace.

Você pode obter o Microsoft.CodeAnalysis.ISymbol do objeto SymbolInfo usando a propriedade SymbolInfo.Symbol. Essa propriedade retorna o símbolo ao qual essa expressão se refere. Para expressões que não se referem a nada (como literais numéricos), essa propriedade é null. Quando o SymbolInfo.Symbol não for nulo, o ISymbol.Kind denota o tipo do símbolo. Neste exemplo, a ISymbol.Kind propriedade é uma SymbolKind.Namespace. Adicione o código a seguir ao método Main. Ele recupera o símbolo do System namespace e exibe todos os namespaces filho declarados no System namespace:

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

Execute o programa e você deverá ver a seguinte saída:

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 namespaces que são um namespace filho do System namespace. Ele exibe todos os namespaces presentes nesta compilação, que faz referência apenas ao assembly onde System.String é declarado. Os namespaces declarados em outros assemblies não são conhecidos por essa compilação

Associar uma expressão

O código anterior mostra como localizar um símbolo associando-se a um nome. Há outras expressões em um programa C# que podem ser associadas que não são nomes. Para demonstrar essa funcionalidade, vamos acessar a associação a um literal de cadeia de caracteres simples.

O programa "Olá, Mundo" contém uma Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxcadeia de caracteres "Olá, Mundo!" exibida no console.

Você encontra a cadeia de caracteres "Olá, Mundo!", localizando o literal de cadeia de caracteres única no programa. Em seguida, depois de localizar o nó de sintaxe, obtenha as informações de tipo para esse nó do 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);

O Microsoft.CodeAnalysis.TypeInfo struct inclui uma TypeInfo.Type propriedade que permite o acesso às informações semânticas sobre o tipo do literal. Neste exemplo, esse é o string tipo. Adicione uma declaração que atribua essa propriedade a uma variável local:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Para concluir este tutorial, vamos criar uma consulta LINQ que cria uma sequência de todos os métodos públicos declarados no string tipo que retornam um string. Essa consulta fica complexa, então vamos compilá-la linha por linha e reconstruí-la como uma única consulta. A origem dessa 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, portanto, filtre-a usando o ImmutableArray<T>.OfType método para localizar elementos que são Microsoft.CodeAnalysis.IMethodSymbol objetos:

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

Em seguida, adicione outro filtro para retornar apenas os métodos que são públicos e retornar um string:

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

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

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

Você também pode criar a consulta completa usando a sintaxe de consulta LINQ e exibir todos os nomes de método no console:

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

Compile e execute o programa. Você deve 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 . . .

Você usou a API Semântica para localizar e exibir informações sobre os símbolos que fazem parte deste programa.