Partilhar via


Introdução à análise semântica

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

Neste tutorial, vai explorar as APIs de Símbolo e Enlace. Estas APIs fornecem informações sobre o significado semântico de um programa. Permitem-lhe fazer e responder a perguntas sobre os tipos representados por qualquer símbolo no seu programa.

Terá de instalar o SDK da Plataforma de Compilador .NET:

Instruções de instalação – Instalador do Visual Studio

Existem duas formas diferentes de encontrar o SDK da Plataforma de Compilador .NET no Instalador do Visual Studio:

Instalar com o Instalador do Visual Studio – Vista de cargas de trabalho

O SDK da Plataforma de Compilador .NET não é selecionado automaticamente como parte da carga de trabalho de desenvolvimento da extensão do Visual Studio. Tem de selecioná-lo como um componente opcional.

  1. Executar o Instalador do Visual Studio
  2. Selecione Modificar
  3. Verifique a carga de trabalho de desenvolvimento da extensão do Visual Studio .
  4. Abra o nó de desenvolvimento da extensão do Visual Studio na árvore de resumo.
  5. Selecione a caixa do SDK da Plataforma do Compilador .NET. Irá encontrá-lo pela última vez nos componentes opcionais.

Opcionalmente, também vai querer que o editor DGML apresente gráficos no visualizador:

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

Instalar com o separador Instalador do Visual Studio – Componentes individuais

  1. Executar o Instalador do Visual Studio
  2. Selecione Modificar
  3. Selecione o separador Componentes individuais
  4. Selecione a caixa do SDK da Plataforma do Compilador .NET. Irá encontrá-lo na parte superior, na secção Compiladores, ferramentas de compilação e runtimes .

Opcionalmente, também vai querer que o editor DGML apresente gráficos no visualizador:

  1. Selecione a caixa do editor DGML. Irá encontrá-lo na secção Ferramentas de código .

Compreender Compilações e Símbolos

À medida que trabalha mais com o SDK do Compilador .NET, familiariza-se com as distinções entre a API de Sintaxe e a API Semântica. A API de Sintaxe permite-lhe ver a estrutura de um programa. No entanto, muitas vezes quer informações mais ricas sobre a semântica ou o significado de um programa. Embora um ficheiro de código solto ou fragmento de código Visual Basic ou C# possa ser analisado sintaticamente isoladamente, não é significativo fazer perguntas como "qual é o tipo desta variável" num vácuo. O significado de um nome de tipo pode depender de referências de assemblagem, importações de espaços de nomes ou outros ficheiros de código. Estas perguntas são respondidas com 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 Visual Basic ou C#. A compilação inclui o conjunto de ficheiros de origem a compilar, referências de assemblagem e opções de compilador. Pode determinar o significado do código através de todas as outras informações neste contexto. A Compilation permite-lhe encontrar Símbolos – entidades como tipos, espaços de nomes, membros e variáveis a que os nomes e outras expressões se referem. O processo de associação de nomes e expressões com Símboloschama-se Enlace.

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

Símbolos de consulta

Neste tutorial, vai voltar a ver o programa "Hello World". Desta vez, vai consultar os símbolos no programa para compreender os tipos que esses símbolos representam. Pode consultar os tipos num espaço de nomes e aprender a encontrar os métodos disponíveis num tipo.

Pode ver o código concluído para este exemplo no nosso repositório do GitHub.

Nota

Os tipos de Árvore de Sintaxe utilizam a herança para descrever os diferentes elementos de sintaxe que são válidos em diferentes localizações do programa. Utilizar estas APIs geralmente significa propriedades de casting ou membros da coleção para tipos derivados específicos. Nos exemplos seguintes, a atribuição e os moldes são instruções separadas, utilizando variáveis explicitamente digitadas. Pode ler o código para ver os tipos de retorno da API e o tipo de runtime dos objetos devolvidos. Na prática, é mais comum utilizar variáveis implicitamente digitadas e depender de nomes de API para descrever o tipo de objetos que estão a ser examinados.

Crie um novo projeto C# Stand-Alone Code Analysis Tool :

  • No Visual Studio, selecione Ficheiro>Novo>Projeto para apresentar a caixa de diálogo Novo Projeto.
  • Em Visual C#>Extensibility, selecione Ferramenta de Análise de Código Autónoma.
  • Dê o nome "SemanticQuickStart" ao projeto e clique em OK.

Vai analisar o programa básico "Hello World!" apresentado anteriormente. Adicione o texto do programa Hello World como uma constante na sua Program turma:

        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 seguinte código 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 uma CSharpCompilation a partir da árvore que já criou. O exemplo "Hello World" baseia-se nos String tipos eConsole. Tem de referenciar a assemblagem que declara esses dois tipos na compilação. Adicione a seguinte linha ao método Main para criar uma compilação da árvore de sintaxe, incluindo a referência à assemblagem adequada:

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

Consultar o modelo semântico

Assim que tiver um Compilation , pode pedir-lhe um SemanticModel para qualquer SyntaxTree um contido nesse Compilation. Pode considerar o modelo semântico como a origem para todas as informações que normalmente obteria do intellisense. A SemanticModel pode responder a perguntas como "Que nomes estão no âmbito nesta localização?", "Que membros estão acessíveis a partir deste método?", "Que variáveis são utilizadas neste bloco de texto?" e "A que se refere este nome/expressão?" Adicione esta instrução para criar o modelo semântico:

SemanticModel model = compilation.GetSemanticModel(tree);

Vincular um nome

O Compilation cria a SemanticModel partir de SyntaxTree. Depois de criar o modelo, pode consulta-lo para encontrar a primeira using diretiva e obter as informações de símbolo do System espaço de nomes. Adicione estas duas linhas ao seu Main método para criar o modelo semântico e obter o símbolo para a primeira instrução de utilização:

// 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 using diretiva para obter um Microsoft.CodeAnalysis.SymbolInfo para o System espaço de nomes. O código anterior também ilustra que utiliza o modelo de sintaxe para localizar a estrutura do código; utiliza o modelo semântico para compreender o respetivo significado. O modelo de sintaxe encontra a cadeia System na instrução using. O modelo semântico tem todas as informações sobre os tipos definidos no System espaço de nomes.

A partir do SymbolInfo objeto, pode obter a Microsoft.CodeAnalysis.ISymbol propriedade com a SymbolInfo.Symbol propriedade . 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 indica o tipo do símbolo. Neste exemplo, a ISymbol.Kind propriedade é um SymbolKind.Namespace. Adicione o seguinte código ao seu Main método. Obtém o símbolo do System espaço de nomes e, em seguida, apresenta todos os espaços de nomes subordinados declarados no System espaço de nomes:

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

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

Nota

O resultado não inclui todos os espaços de nomes que são um espaço de nomes subordinado do System espaço de nomes. Apresenta todos os espaços de nomes presentes nesta compilação, que só referenciam a assemblagem onde System.String é declarada. Quaisquer espaços de nomes declarados noutras assemblagens não são conhecidos desta compilação

Vincular uma expressão

O código anterior mostra como localizar um símbolo ao vincular a um nome. Existem outras expressões num programa C# que podem ser vinculadas que não são nomes. Para demonstrar esta capacidade, vamos aceder ao enlace a um literal de cadeia simples.

O programa "Hello World" contém uma Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxcadeia " Hello, World!" apresentada na consola do .

Encontrará a cadeia "Hello, World!" ao localizar o literal de cadeia única no programa. Em seguida, depois de localizar o nó de sintaxe, obtenha as informações do tipo para esse nó a partir 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);

A Microsoft.CodeAnalysis.TypeInfo estrutura inclui uma TypeInfo.Type propriedade que permite o acesso às informações semânticas sobre o tipo de literal. Neste exemplo, é esse o string tipo. Adicione uma declaração que atribui esta 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 devolve um string. Esta consulta torna-se complexa, por isso vamos compilá-la linha a linha e, em seguida, reconstruí-la como uma única consulta. A origem desta 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, pelo que deve filtrá-la com o ImmutableArray<T>.OfType método para localizar elementos que sejam Microsoft.CodeAnalysis.IMethodSymbol objetos:

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

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

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

Selecione apenas a propriedade name e apenas os nomes distintos ao remover quaisquer sobrecargas:

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

Também pode criar a consulta completa com a sintaxe da consulta LINQ e, em seguida, apresentar todos os nomes dos métodos na consola do :

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

Crie e execute 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 . . .

Utilizou a API Semântica para localizar e apresentar informações sobre os símbolos que fazem parte deste programa.