Source Generators

Введение в Source Generators

Последнее обновление: 26.11.2022

Начиная с версии .NET 5 во фреймворк была добавлена такая функциональность, как Source Generators (генераторы кода), которая позволяет разработчикам анализировать уже имеющийся код и динамически генерировать новый код в процессе компиляции.

Определение генератора кода

Source Generator/генератор кода представляет класс, который реализует интерфейс Microsoft.CodeAnalysis.ISourceGenerator и применяет атрибут Microsoft.CodeAnalysis.GeneratorAttribute:

using Microsoft.CodeAnalysis;

[Generator]
public class MySourceGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        throw new NotImplementedException();
    }
    public void Initialize(GeneratorInitializationContext context)
    {
        throw new NotImplementedException();
    }
}

интерфейс ISourceGenerator предоставляет два метода:

  • Метод Initialize() выполняет инициализацию и вызывается перед генерацией кода. Его параметр context применяется для регистрации обработчиков событий, которые возникают при генерации кода

  • Метод Execute() выполняет генерацию исходного кода. Его параметр context применяется для добавления файлов с исходным кодом

Рассмотрим создание и применение простейшего генератора кода.

Создание библиотеки классов для Source Generators

Для хранения генератора кода создадим новый проект библиотеки классов, который, пусть называется GeneratorsLib.

Для работы с Source Generators прежде всего добавим в проект библиотеки классов Nuget-пакеты Microsoft.CodeAnalysis.Analyzers и Microsoft.CodeAnalysis.CSharp

Следует учитывать, что на данный момент генераторы кода поддерживаются только для целевого фреймворка "netstandard2.0". Поэтому изменим csproj-файл проекта следующим образом:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
  </ItemGroup>

</Project>

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

using Microsoft.CodeAnalysis;

namespace GeneratorsLib
{
    [Generator]
    public class MySourceGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            // определяем генерируемый код
            var code = @"
            namespace Metanit
            {
                public static class Welcome 
                {
                    public const string Name = ""Eugene"";
                    public static void Print() => Console.WriteLine($""Hello {Name}!"");
                }
            }";
            context.AddSource("metanit.welcome.generated.cs",code);
        }
        public void Initialize(GeneratorInitializationContext context)
        {
            // инициализация не нужна
        }
    }
}

Здесь в методе Execute() выполняем генерацию кода. Генерируемый код фактически представляет переменную code. В этом коде определяется стандартный код C# - в пространстве имен Metanit располагается статический класс Welcome, который имеет метод Print. Метод Print выводит на консоль приветствие.

Чтобы этот код принял участие в компиляции, у объекта context вызываем метод AddSource(). Первый параметр этого метода представляет уникальное имя файла, который будет генерироваться. Обычно он завершается на ".g.cs" или на ".generated.cs". Второй параметр представляет строку кода.

Стоит отметить, что в данном случае не применяется инициализация, поэтому метод Initialize пуст.

Применение Source Generator

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

Source Generators в проекте на C# и .NET

После добавления ссылки на проект библиотеки классов csproj-файл консольного проекта должен содержать ссылку на проект в следующем виде:

<ItemGroup>
	<ProjectReference Include="..\Путь_к_проекту_библиотеки_классов\GeneratorsLib.csproj"/>
</ItemGroup>

Изменим этот код следующим образом:

<ItemGroup>
	<ProjectReference Include="..\Путь_к_проекту_библиотеки_классов\GeneratorsLib.csproj"
                        OutputItemType="Analyzer" 
                        ReferenceOutputAssembly="false" />
</ItemGroup>

В данном случае у элемента ProjectReference устанавливаются два дополнительных атрибута. Атрибут OutputItemType указывает на тип проекта - он должен иметь значение "Analyzer". А атрибут ReferenceOutputAssembly получает значение false, которое указывает, что скомпилированное консольное приложение не будет содержать ссылку на этот проект библиотеки классов.

И в конце изменим в проекте консольного приложения файл Program.cs следующим образом:

Metanit.Welcome.Print();

То есть наш генератор генерирует пространство имен Metanit, в котором есть класс Welcome с методом Print. И в данном случае мы вызываем этот метод. Построим проект и запустим консольный проект на выполнение, и будет выполняться метод Metanit.Welcome.Print() из сгенерированного кода:

Hello Eugene!

(Если Visual Stuidio подчеркивает строку кода как некорректную, то лучше выполнить построение проекта и перезапустить Visual Studio).

Если используется Visual Studio, то мы можем увидеть сгенерированные файлы. Так, в структуре проекта перейдем к узлу Dependencies -> Analyzers -> GeneratorsLib и там мы сможем увидеть сгенерированный файл и посмотреть его содержимое.

Dependencies и Analyzers в Visual Studio и исходный код Source Generators в C#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850