Найти в Дзене
ИРОКЕЗ КОНФОРМИСТА

Путешествие в Source Generator. Часть 1

Оглавление

Мои знакомцы из чата, в котором супер изучают супер все, предложили мне взять в качестве темы следующей заметки в блог "кодогенерацию в dotnet". Тема эта весьма большая и сложная. Это значит, что она требует много времени на освоение да и в качестве простенькой заметки в блог не подойдет. Посидев и подумав, я решил сделать серию заметок, которые будут отмечать пройденные мной вехи. Как и всякий другой dotnet путешественник, первым делом я решил заглянуть в документацию Microsoft. Но так как я еще и современный путешественник, то решил асинхронно поинтересоваться у ChatGPT про то “Что такое и зачем нужны генераторы исходного кода времени компиляции в dotnet”:

Source Generators (генераторы исходного кода) в dotnet - это новый механизм, добавленный в версию .NET 5, который позволяет генерировать дополнительный исходный код во время компиляции на основе анализа уже существующего кода. Этот новый механизм позволяет разработчикам генерировать исходный код для различных целей, например, для упрощения API, уменьшения дублирования кода, выполнения повторяющихся задач и многого другого. Source Generators работают за счет использования новой функциональности компилятора Roslyn для считывания информации из исходного кода и генерации новых файлов C# на основе этой информации. Они могут генерировать как простой код, так и сложные конструкции, а также могут использовать сторонние библиотеки и инструменты, такие как Roslyn API, TextTemplating и т.д.

В статье нам говорят, что быть такими крутыми Source Generators позволяют три вещи:

  • Рефлексия;
  • Жонглирование MSBuild тасками;
  • Вплетение (в оригинале weaving) IL кода.

Рефлексия?

Суть в том, чтобы использовать механизм рефлексии не во время исполнения, а на этапе компиляции. Авторы статьи приводят пример с обнаружением контроллеров и razor pages при запуске asp.net приложения. Я же предлагаю вашему вниманию два проекта, использующих генерацию исходников вместо рефлексии времени выполнения:

Жонглирование тасками?

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

Вплетение?

IL weaving - это процесс изменения байт-кода, который создается компилятором .NET. Во время компиляции исходного кода на C# или другом языке создается оптимизированный код на IL. При этом процессе можно добавлять новый функционал, например, встраивать аспекты. Аспекты - это специальный тип программного обеспечения, который добавляет дополнительные возможности, не изменяя исходный код программы. К примеру, этот процесс может использоваться библиотекой PostSharp (надо бы ознакомиться), которая помогает разработчикам создавать аспектно-ориентированные программы.

Свой генератор исходников!

Далее нам предлагают попробовать написать свой “Hello world”, но не простой, а с генерацией (забавно, что делают они это на старом шаблоне, а не на top level statement-ах, вероятнее всего просто в рамках примера). Я завел репозиторий, в который буду заносить свои “генераторы” по ходу погружения в тему. Код простейшего генератора можно найти там же. Думаю, что и в дальнейшем буду поддерживать структуру, что в солюшене будет директория, в которой будет два проекта:

  • Generator с кодом генератора;
  • Demo с кодом использующим генератор.

Коротко опишу код, но в целом от кода из статьи microsoft он мало чем отличается.
Program.cs в проекте Demo:

namespace Demo;

partial class Program
{

static void Main(string[] args)
{
Console.WriteLine("!!!");
HelloFrom("it-irokez.ru");
}
static partial void HelloFrom(string name);
}

Обратите внимание на то, что класс Program объявлен как partial. Смысла в этом не много, так просто проще сделать в рамках простейшего демонстрационного варианта.

Код нашего генератора:

using Microsoft.CodeAnalysis;
namespace Generator
{
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// Берем точку входа в сборку
var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
string source = $@"// <auto-generated/>
using System;

namespace {mainMethod.ContainingNamespace.ToDisplayString()}
{{
public static partial class {mainMethod.ContainingType.Name}
{{
static partial void HelloFrom(string name) =>
Console.WriteLine($""Barber says: Hi from '{{name}}', let's make irokezzzzzz!"");
}}
}}
";
var typeName = mainMethod.ContainingType.Name;

// Add the source code to the compilation
context.AddSource($"{typeName}.g.cs", source);
}

public void Initialize(GeneratorInitializationContext context)
{

}
}
}

Прежде всего, задается имя генератора с помощью атрибута [Generator]. Этот атрибут указывает, что данный класс является генератором исходного кода. После регистрации генератора, он будет запущен во время компиляции проекта. Компилятор вызывает метод Initialize у каждого зарегистрированного генератора, а затем вызывает метод Execute. Метод Execute является основным методом генератора исходного кода и выполняет логику генерации нового исходного кода на основе уже существующего. Для реализации логики генератора в метод Execute передается объект GeneratorExecutionContext , с его помощью можно получить доступ к коллекции классов представленных объектами класса INamedTypeSymbol, к объектам SemanticModel и SyntaxTree, которые представляют символьную и синтаксическую информацию об анализируемой сборке. С их помощью генератор получает доступ к методам, именам классов, пространствам имен, переменным и др. В данном случае, генерируется простой исходный код, который выводит сообщение на консоль. Заканчивается метод вызовом метода context.AddSource, чтобы добавить сгенерированный код в компиляцию проекта. В параметрах метода указывается имя файла, который будет создан с помощью сгенерированного кода.

Заметки и комметнарии к генератору

  • Делайте регулярно clean и rebuild
  • TargetFramework почему-то должен быть исключительно netstandard2.0 иначе не работает. Есть даже такой, ничего не объясняющий совет:

Я думаю, что это связано с обратной совместимостью.

  • Microsoft в зависимостях заявляет Microsoft.CodeAnalysis.CSharp и Microsoft.CodeAnalysis.Analyzer , но и без второго все работает.

Заключение

Сегодня мы с вами прошлись по вводной статье от Microsoft для погружения в мир генераторов исходников в dotnet. Мы узнали что такое Source generators, написали свой генератор и издали потыкали палкой в подводные камни на этом пути. В следующий раз я намереваюсь ознакомиться с кулинарной книгой в которой собраны рецепты по приготовлению типовых кодогенерированных кейсов.