← Все статьи

Паттерн Source Generator в C#: автоматизация рутины

Если вы пишете на C#, вы наверняка сталкивались с рутиной, которая отнимает время и засоряет проект шаблонным кодом. Это могут быть модели для ORM, DTO-объекты, реализации INotifyPropertyChanged или даже простые ToString() методы для сложных классов. Традиционно эту проблему решали рефлексией во время выполнения или кодогенерацией через T4-шаблоны на этапе разработки. Но у обоих подходов есть изъяны: рефлексия бьет по производительности, а внешние генераторы усложняют сборку. В современном C# появилось элегантное решение, встроенное прямо в компилятор — Source Generators.

Source Generator — это компонент, который запускается в процессе компиляции и может анализировать ваш код, чтобы добавить к нему новый исходный код. Представьте себе: вы пишете частичный класс с несколькими свойствами, а генератор, видя это, автоматически создает второй файл с этим же частичным классом, но уже дополненным полноценной реализацией интерфейса INotifyPropertyChanged, со всеми вызовами PropertyChanged?.Invoke(). И все это происходит мгновенно, до того как компилятор Roslyn приступит к основной работе. Вы получаете готовый код без единой строчки ручного написания.

Чем этот подход принципиально лучше? Во-первых, нулевые накладные расходы во время выполнения. Сгенерированный код — это обычный C#, который компилируется в ту же сборку. Никакой рефлексии или динамических методов. Во-вторых, полная типобезопасность. Поскольку генератор работает с синтаксическими деревьями и семантической моделью Roslyn, он «понимает» типы так же глубоко, как и сам компилятор. Ошибки будут видны сразу при сборке. В-третьих, интеграция в среду разработки. Современные IDE, такие как Visual Studio 2022 или Rider, могут показывать сгенерированный код для инспекции (обычно в разделе Analyzers), что делает процесс прозрачным.

Давайте рассмотрим практический пример из реальной жизни — генерацию расширенных методов ToString() для классов-сущностей. Допустим, у вас есть доменная модель с десятками классов типа Customer, Order, Product. Для логирования и отладки вам нужны информативные строковые представления этих объектов. Писать их вручную — скучно и чревато ошибками при изменении класса.

Создадим простой Source Generator. Он будет искать все классы в проекте, помеченные специальным атрибутом [GenerateToString], и для каждого такого класса генерировать переопределение метода ToString(), которое включает значения всех публичных свойств.

Вот схематичное описание логики генератора: 1. Генератор создает синтаксический ресивер (ISyntaxReceiver), который на этапе предварительного анализа собирает все объявления классов с нужным атрибутом. 2. Для каждого найденного класса генератор запрашивает его семантическую модель, чтобы получить полную информацию о всех его публичных свойствах. 3. На основе этой информации строится исходный код нового частичного класса.

Пример итогового сгенерированного кода для класса Customer мог бы выглядеть так: public partial class Customer { public override string ToString() { return $"Customer {{ Id = {Id}, Name = {Name}, Email = {Email} }}"; } }

Вы просто добавляете атрибут [GenerateToString] над своим классом Customer и получаете готовый метод при следующей сборке.

Но настоящая сила Source Generators раскрывается в более комплексных задачах. Один из самых ярких примеров — библиотека Microsoft.Extensions.Logging. Генераторы используются для создания высокопроизводительных логгеров. Вместо того чтобы передавать строки сообщений и массив аргументов (что ведет к аллокациям памяти), вы помечаете метод логгирования атрибутом [LoggerMessage]. Генератор создаст статический расширяющий метод с использованием LogValuesAndFormat или даже Source Generation Logging в.NET 6+, где сообщение форматируется только если указанный уровень логгирования включен.

Еще одна перспективная область — веб-API и gRPC. Такие фреймворки как MagicOnion используют генераторы для создания клиентских прокси на основе интерфейсов сервиса прямо во время компиляции клиента. Это дает строгую типизацию и исключает ошибки в названиях методов или типах параметров.

  • Отладка может быть сложной: процесс генерации кода скрыт от разработчика.
  • Порядок выполнения: если в проекте несколько генераторов, они выполняются последовательно, но управлять этим порядком не всегда просто.
  • Производительность сборки: сложные генераторы могут замедлить процесс компиляции.
  • Ограниченное взаимодействие: генератор не может изменять существующий пользовательский код — только добавлять новый.
  • Всегда делайте генераторы инкрементальными (используйте IncrementalGenerator), чтобы они перезапускались только при реальных изменениях входных данных.
  • Генерируемый код должен быть максимально простым и прямолинейным; избегайте сложной логики внутри него.
  • Предоставляйте понятные диагностические сообщения (ошибки и предупреждения), если входные данные некорректны.
  • Хорошо документируйте ожидаемое поведение генератора для коллег по команде.

Заключение...

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

💬 Комментарии (0)

Пока нет комментариев