Бөлісу құралы:


Начало работы с семантическим анализом

В этом руководстве предполагается, что вы знакомы с API синтаксиса. В статье о начале работы с синтаксическим анализом содержится достаточное введение.

В этом руководстве вы изучите API символов и привязки. Эти API предоставляют сведения о семантическом значении программы. Они позволяют задавать и отвечать на вопросы о типах, представленных любым символом в программе.

Необходимо установить пакет SDK платформы компилятора .NET:

Инструкции по установке — Visual Studio Installer

Существует два разных способа найти пакет SDK платформы компилятора .NET в установщике Visual Studio:

Установка с помощью установщика Visual Studio — представление рабочих нагрузок

Пакет SDK платформы компилятора .NET не выбирается автоматически в рамках рабочей нагрузки разработки расширений Visual Studio. Его необходимо выбрать как необязательный компонент.

  1. Запуск установщика Visual Studio
  2. Выберите Изменить.
  3. Проверьте рабочую нагрузку разработки расширений Visual Studio .
  4. Откройте узел разработки расширений Visual Studio в дереве сводки.
  5. Убедитесь, что установлен флажок для пакета SDK для платформы компилятора .NET .
  6. Нажмите кнопку Изменить.

Кроме того, вы также хотите, чтобы редактор DGML отображал графы в визуализаторе:

  1. Откройте узел отдельных компонентов в дереве сводки.
  2. Установите флажок для редактора DGML

Установка с помощью установщика Visual Studio — вкладка "Отдельные компоненты"

  1. Запуск установщика Visual Studio
  2. Выберите Изменить.
  3. Выберите вкладку "Отдельные компоненты"
  4. Установите флажок для пакета SDK платформы компилятора .NET. Его можно найти в верхней части раздела "Компиляторы", "Средства сборки" и "Среды выполнения ".
  5. Нажмите кнопку Изменить.

Кроме того, вы также хотите, чтобы редактор DGML отображал графы в визуализаторе:

  1. Установите флажок для редактора DGML. Его можно найти в разделе "Инструменты кода ".

Общие сведения о компиляциях и символах

При работе с пакетом SDK компилятора .NET вы знакомы с различиями между API синтаксиса и семантической API. API синтаксиса позволяет просмотреть структуру программы. Однако часто требуется более подробная информация о семантике или значении программы. Хотя свободный файл кода или фрагмент кода Visual Basic или C# можно синтаксически анализировать в изоляции, не имеет смысла задавать такие вопросы, как "тип этой переменной" в вакууме. Значение имени типа может зависеть от ссылок на сборки, импорта пространства имен или других файлов кода. Эти вопросы отвечают с помощью семантического API, в частности Microsoft.CodeAnalysis.Compilation класса.

Экземпляр Compilation аналогичен одному проекту, как показано компилятором и представляет все необходимое для компиляции программы Visual Basic или C#. Компиляция включает набор исходных файлов для компиляции, ссылок на сборки и параметров компилятора. Вы можете подумать о значении кода, используя все остальные сведения в этом контексте. A Compilation позволяет находить сущности, такие как символы, включая типы, пространства имен, члены и переменные, к которым относятся имена и другие выражения. Процесс связывания имен и выражений с символами называется Привязкой.

Например Microsoft.CodeAnalysis.SyntaxTree, Compilation является абстрактным классом с производными от языка. При создании экземпляра компиляции необходимо вызвать метод фабрики класса Microsoft.CodeAnalysis.CSharp.CSharpCompilation (или Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation).

Запрос символов

В этом руководстве вы снова посмотрите на программу Hello World. На этот раз вы запрашиваете символы в программе, чтобы понять, какие типы символы представляют. Вы запрашиваете типы в пространстве имен и учитесь находить доступные методы в типе.

Готовый код для этого примера можно увидеть в нашем репозитории GitHub.

Замечание

Типы дерева синтаксиса используют наследование для описания различных элементов синтаксиса, допустимых в разных расположениях программы. Использование этих API часто означает приведение свойств или членов коллекции к определенным производным типам. В следующих примерах присваивание и приведение данных выполняются как отдельные инструкции с использованием явно типизированных переменных. Вы можете прочитать код, чтобы просмотреть возвращаемые типы API и тип среды выполнения возвращаемых объектов. На практике чаще используются неявно типизированные переменные и используются имена API для описания типа проверяемых объектов.

Создайте новый проект C# для автономного средства анализа кода.

  • В Visual Studio выберите "Файл>нового проекта">, чтобы отобразить диалоговое окно "Новыйпроект".
  • В разделе "Расширяемость Visual C#"> выберите самостоятельное средство анализа кода.
  • Присвойте проекту имя "SemanticQuickStart" и нажмите кнопку "ОК".

Вы собираетесь проанализировать базовую программу "Hello World!", показанную ранее. Добавьте текст для программы Hello World в качестве константы в Program классе:

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

Затем добавьте следующий код, чтобы создать дерево синтаксиса для текста кода в programText константе. Добавьте следующую строку в Main метод:

SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);

CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Затем создайте CSharpCompilation из дерева, которое вы уже создали. Пример «Hello World» основан на типах String и Console. Необходимо ссылаться на сборку, которая объявляет эти два типа в процессе компиляции. Добавьте следующую строку в Main метод для создания компиляции дерева синтаксиса, включая ссылку на соответствующую сборку:

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

Метод CSharpCompilation.AddReferences добавляет ссылки на компиляцию. Метод MetadataReference.CreateFromFile загружает сборку в качестве ссылки.

Запрос семантической модели

После того как у вас будет Compilation, вы можете запросить у него SemanticModel для любого SyntaxTree, содержащегося в указанном Compilation. Вы можете рассматривать семантику модели как источник для всех сведений, которые вы обычно получаете от intellisense. Выражение SemanticModel может отвечать на вопросы, такие как "Какие имена находятся в области видимости в этой точке?", "Какие элементы доступны из этого метода?", "Какие переменные используются в этом блоке текста?" и "На что ссылается это имя или выражение?" Добавьте это выражение для создания семантической модели.

SemanticModel model = compilation.GetSemanticModel(tree);

Привязка имени

Compilation создает объект SemanticModel из SyntaxTree. После создания модели его можно запросить, чтобы найти первую using директиву и получить сведения о символах для System пространства имен. Добавьте эти две строки в Main метод для создания семантической модели и получения символа для первой using директивы:

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

В приведенном выше коде показано, как связать имя в первой директиве using, чтобы получить элемент Microsoft.CodeAnalysis.SymbolInfo для пространства имен System. Приведенный выше код также иллюстрирует использование модели синтаксиса для поиска структуры кода; вы используете семантику модели для понимания его смысла. Модель синтаксиса находит строку System в директиве using . Семантическая модель содержит все сведения о типах, определенных в System пространстве имен.

Из объекта SymbolInfo можно получить Microsoft.CodeAnalysis.ISymbol с помощью свойства SymbolInfo.Symbol. Это свойство возвращает символ, на который ссылается это выражение. Для выражений, которые не ссылаются ни на что (например, числовые литералы) это свойство null. SymbolInfo.Symbol Если значение не равно NULL, ISymbol.Kind он обозначает тип символа. В этом примере свойство ISymbol.Kind является SymbolKind.Namespace. Добавьте следующий код в Main метод. Он извлекает символ для System пространства имен, а затем отображает все дочерние пространства имен, объявленные в System пространстве имен:

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

Запустите программу и вы увидите следующие выходные данные:

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

Замечание

Выходные данные не включают все пространства имен, которые являются дочерними для пространства имен System. Отображаются все пространства имен, присутствующие в данной компиляции, которая ссылается только на сборку, где объявляется System.String. Все пространства имен, объявленные в других сборках, не известны этой компиляции

Привязка выражения

В приведенном выше коде показано, как найти символ путем привязки к имени. В программе C# существуют другие выражения, которые могут быть привязаны, и они не обязательно являются именами. Чтобы продемонстрировать эту возможность, давайте перейдем к привязке к простому строковому литералу.

Программа Hello World содержит Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntaxстроку "Hello, World!", отображаемую в консоли.

Вы найдете строку "Hello, World!", найдя единственный строковый литерал в программе. Затем после того, как вы нашли узел синтаксиса, получите сведения о типе для этого узла из семантической модели. Добавьте следующий код в Main метод:

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

Структура Microsoft.CodeAnalysis.TypeInfo включает в себя свойство TypeInfo.Type, которое позволяет получить доступ к семантической информации о типе литерала. В этом примере тип — это string. Добавьте объявление, которое назначает это свойство локальной переменной:

var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;

Чтобы завершить работу с этим руководством, давайте создадим запрос LINQ, который создает последовательность всех открытых методов, объявленных в типе string, которые возвращают string. Данный запрос становится сложным, поэтому давайте построим его построчно, а затем соберем в виде одного полного запроса. Источником этого запроса является последовательность всех членов, объявленных в типе string :

var allMembers = stringTypeSymbol?.GetMembers();

Эта исходная последовательность содержит все элементы, включая свойства и поля, поэтому фильтруйте его с помощью ImmutableArray<T>.OfType метода для поиска элементов, являющихся Microsoft.CodeAnalysis.IMethodSymbol объектами:

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

Затем добавьте еще один фильтр, чтобы вернуть только те методы, которые являются публичными и возвращают string:

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

Выберите только свойство name и только отдельные имена, удаляя все перегрузки:

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

Вы также можете создать полный запрос с помощью синтаксиса запроса LINQ, а затем отобразить все имена методов в консоли:

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

Создайте и запустите программу. Вы увидите следующие выходные данные:

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

Вы использовали семантический API для поиска и отображения сведений о символах, входящих в эту программу.