JavaRush /Курсы /C# SELF /Введение в Source Generato...

Введение в Source Generators

C# SELF
63 уровень , 3 лекция
Открыта

1. Введение

Когда вы пишете код, наверняка иногда ловили себя на мысли: «Почему я снова и снова копирую один и тот же шаблон кода для разных классов?» Или: «Почему сериализация, логгирование, маппинг данных — это так много однотипных строк?» Порой хочется, чтобы кто-то (или что-то) написал этот трудоёмкий шаблон за вас.

Вот тут и выходят на сцену Source Generators — новая возможность в C#, появившаяся в .NET 5 и активно развивающаяся до сих пор. Source Generator — это библиотека, которая запускается на этапе компиляции и может динамически генерировать C#-код, который автоматически встраивается в ваш проект до финальной сборки.

Зачем это нужно?

  • Автоматизация рутины: Избавляет от написания однотипных классов/методов (boilerplate).
  • Безопасность, проверяемая компилятором: Генерируемый код компилируется вместе с вашим (в отличие от T4 или рефлексии).
  • Высокая производительность: Сериализация, DI, маппинг и др. без затрат на рефлексию в рантайме.
  • Поддержка современных паттернов: Реализация подходов, которые были сложны/дороги без генерации кода.

Как Source Generators работают «под капотом»?

Source Generator — это .NET-библиотека (обычно проект типа Class Library), реализующая интерфейс ISourceGenerator. Во время компиляции Roslyn запускает все подключённые генераторы, предоставляя им доступ к синтаксическому дереву вашего кода.

Генератор анализирует ваш код, решает, что и где нужно сгенерировать, и создаёт новые C#-файлы, которые компилятор тут же компилирует.

Автоматическая генерация ToString

Давайте начнём с простого. Представим, что у нас есть класс со множеством свойств, и нам нужно реализовать ToString. Рукописно это выглядит так:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString()
        => $"Person(Name={Name}, Age={Age})";
}

Но если свойств становится очень много — становится лень, а ещё можно легко забыть что-то обновить. Source Generator может сделать это за вас!

2. Как создать свой Source Generator?

Создание проекта

Откройте JetBrains Rider или Visual Studio, создайте новый проект типа Class Library (.NET Standard) — именно такие проекты могут быть генераторами. Затем добавьте NuGet-пакеты:

  • Microsoft.CodeAnalysis.CSharp
  • Microsoft.CodeAnalysis.Analyzers

Важные атрибуты

  • [Generator] — указывает, что этот класс является Source Generator.

Простейший шаблон генератора

Вот минимальный рабочий пример:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Можно зарегистрировать дополнительные действия (необязательно)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var code = @"
namespace Generated
{
    public static class HelloWorld
    {
        public static string SayHello() => ""Привет, мир! Я сгенерирован!"";
    }
}";
        context.AddSource("HelloWorldGenerator", SourceText.From(code, Encoding.UTF8));
    }
}

Этот простой генератор всегда добавляет статический класс HelloWorld с методом SayHello во время компиляции.

Как использовать Source Generators в основном приложении?

Подключите проект-генератор как NuGet-пакет или Project Reference в секции Analyzer (подробнее — официальная документация).

Сгенерированный код сразу доступен в вашем проекте — не нужно ничего дополнительно подключать, просто используйте:

// Это сгенерируется автоматически!
using Generated;

Console.WriteLine(HelloWorld.SayHello());

3. Реальный пример: автоматическая генерация ToString

Предположим, мы хотим, чтобы все классы, помеченные атрибутом [AutoToString], автоматически получали реализацию метода ToString. Для этого понадобится:

  • Создать свой атрибут.
  • Проанализировать все классы с этим атрибутом.
  • Для каждого такого класса сгенерировать метод ToString.

Атрибут

[AttributeUsage(AttributeTargets.Class)]
public class AutoToStringAttribute : Attribute
{
}

Использование в коде

[AutoToString]
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

Простая логика генерации

Генератор будет искать классы с [AutoToString] и генерировать примерно такой код:

public override string ToString() 
    => $"Product(Name={Name}, Price={Price})";

Кусочек реального кода генератора

Базовая идея — обход синтаксического дерева с Roslyn:

public void Execute(GeneratorExecutionContext context)
{
    // Анализируем все синтаксические деревья
    foreach (var tree in context.Compilation.SyntaxTrees)
    {
        var root = tree.GetRoot();
        // Ищем все классы с нужным атрибутом (примерно!)
        var classes = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .Where(c => c.AttributeLists
                         .SelectMany(al => al.Attributes)
                         .Any(a => a.Name.ToString().Contains("AutoToString")));

        foreach (var @class in classes)
        {
            var className = @class.Identifier.Text;
            // Получаем все свойства класса
            var props = @class.Members
                .OfType<PropertyDeclarationSyntax>()
                .Select(p => p.Identifier.Text)
                .ToArray();

            var toStringCode = string.Join(", ", props.Select(p => $"{p}={{this.{p}}}"));
            var generated = $@"
partial class {className}
{{
    public override string ToString() => $""{className}({toStringCode})"";
}}";

            context.AddSource($"{className}_ToString", SourceText.From(generated, Encoding.UTF8));
        }
    }
}

Обратите внимание: для production-кода применяют более корректный анализ через SemanticModel Roslyn.

4. Полезные нюансы

На что стоит обращать внимание

Source Generators не могут модифицировать существующий исходный код — только создавать новые файлы (например, дополнительные partial-классы, методы и т.д.). Это значит, что если ваш класс объявлен как partial, можно сгенерировать для него дополнительные методы или свойства.

Иногда бывает сложно корректно разбирать синтаксис и учитывать все нюансы языка (вложенные классы, обобщения, модификаторы и пр.). Автор генератора должен следить, чтобы сгенерированный код был компилируемым и не «ломал» проект.

Ещё одна ловушка — если вы генерируете методы, реализующие интерфейс, убедитесь, что файлы генерируются при каждой сборке заново. Иначе возможны странные ошибки компиляции. Современные инструменты обычно решают эти проблемы, но помнить об этом важно.

Source Generators vs. рефлексия

Рефлексия: вызывается в рантайме, затратно по ресурсам, не проверяется компилятором, часто медленно на больших объёмах данных.

Source Generator: генерирует код на этапе компиляции. Всё статически проверяется, IDE видит методы, работает автодополнение, а производительность как у обычного C#-кода.

Практическая польза

  • System.Text.Json: генерация сериализации/десериализации без рефлексии.
  • Проектирование DI-контейнеров: например, Microsoft.Extensions.DependencyInjection с генерацией графа зависимостей.
  • Мапперы типа Mapster: переход от рефлексии к compile-time генерации mapping-кода.
  • Тестовые фреймворки: автогенерация тестовых методов на основе атрибутов.
  • ASP.NET Minimal APIs (начиная с .NET 7): генерация endpoint-хендлеров.

Конфигурация, параметры и опции

Генераторы можно настраивать через параметры MSBuild, дополнительные файлы и конвенции. Можно, например, генерировать разный ToString в зависимости от окружения (Debug/Release) или конфигурации приложения.

Как Source Generators связаны с реальными задачами

Для разработчика, стремящегося к чистому и быстрому коду, это отличный инструмент: меньше рутины, больше compile-time проверок, подсказки IDE и понятный рефакторинг. Знание генераторов всё чаще спрашивают на собеседованиях — от сериализации и DI до маппинга.

Жизненный цикл Source Generator

Стадия Что происходит
1. Проект подключён Ваш генератор добавлен как analyzer/reference
2. Roslyn компилирует исходник Генератор получает AST (абстрактное синтаксическое дерево)
3. Генератор исполняется Добавляет новые .cs-файлы для компиляции
4. Всё компилируется Сгенерированные файлы становятся частью вашей сборки
5. Код готов! Сгенерированные методы/классы доступны для вызова

5. Отладка и типичные ошибки

Одна из самых частых ошибок новичка при написании генераторов — забыть про ключевое слово partial у класса, в который вы хотите дописать код. Если не указать partial, компилятор просто не увидит ваши изменения. Иногда сгенерированный файл может не подхватиться в IDE до первой пересборки — не пугайтесь.

Будьте осторожны при именовании сгенерированных файлов: если дать всем один и тот же Name, они затрут друг друга. Лайфхак: в имя файла можно подставлять имя класса, для которого генерируется код — например, context.AddSource($"{className}_ToString", ...).

Ошибка с двойным импортом атрибутов — если сгенерировать класс с атрибутом, который уже есть в основном проекте, возникнет конфликт. Лучше выносить необходимые атрибуты в общий проект или добавлять сгенерированный код только при необходимости.

2
Задача
C# SELF, 63 уровень, 3 лекция
Недоступна
Автоматическая генерация методов ToString
Автоматическая генерация методов ToString
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ