Опубликовано
- 2 мин чтения
Генераторы кода в C#

В ноябре я рассказывал про EventFlow — библиотеку, предназначенную для DDD и Event Sourcing. Наша команда активно её использует. Спустя несколько месяцев работы с ней я заметил, что часто приходится писать повторяющийся код, что довольно утомительно.
Например, чтобы создать событие для OrderAggregate
, нужно каждый раз указывать тип агрегата и тип его ID:
public class OrderCreated :
IAggregateEvent<OrderAggregate, OrderAggregateId>
При подписке на события снова нужно указывать тип события, агрегата и ID:
public class OrderAggregateSubscribers :
ISubscribeSynchronousTo<OrderAggregate, OrderAggregateId, OrderCreated>
Как избавиться от шаблонного кода?
Можно объявить абстрактный класс для всех событий агрегата и использовать его:
public abstract class OrderAggregateEvent :
IAggregateEvent<OrderAggregate, OrderAggregateId>;
Создать новый интерфейс, принимающий только тип события:
public interface ISubscribeSynchronousTo<TEvent> :
ISubscribeSynchronousTo<OrderAggregate, OrderAggregateId, TEvent>
Но даже в этом случае всё равно приходится вручную дублировать код для каждого агрегата. А как гласит известная айтишная мудрость:
Никогда не тратьте 10 минут, чтобы сделать что-то вручную, если можно потратить 10 часов пытаясь это автоматизировать.
Поэтому в начале февраля я занялся написанием генератора кода для EventFlow. Через 2 недели и 7 коммитов библиотека EventFlow.SourceGenerators была готова и на днях Расмус Микелсен (автор EventFlow) принял изменения.
Как помогают генераторы кода?
Теперь вся рутинная работа ложится на компилятор, и можно писать меньше кода, который к тому же лучше читается.
// Добавляем атрибут AggregateExtensions к агрегату
[AggregateExtensions]
public class OrderAggregate(OrderAggregateId id) :
AggregateRoot<OrderAggregate, OrderAggregateId>(id)
// Описание событий — OrderAggregateEvent генерируется автоматически
public class OrderCreated : OrderAggregateEvent;
// Подписка на события — новый интерфейс генерируется автоматически
public class OrderAggregateSubscribers :
ISubscribeSynchronousTo<OrderCreated>
Как видите, код стал заметно чище, и теперь нам не приходится снова и снова указывать тип агрегата и тип ID.
Сложности при работе с генераторами кода
Стоит отметить, что генераторы кода — непростая тема. Существует множество ограничений и сложностей. Например, с чем я столкнулся:
- Генераторы можно писать только под .NET Standard 2.0.
- Возможны коллизии названий сгенерированных объектов.
- Тестирование сгенерированного кода имеет свои особенности.
Что дальше?
Если вас заинтересовали генераторы кода, советую начать с видео от Microsoft.
Затем изучите следующие материалы:
- Creating an incremental generator.
- Introducing C# Source Generators.
- Incremental Generators Cookbook.
В качестве примера можно заглядывать в исходный код библиотеки EventFlow.SourceGenerators и её тестов. Она довольна простая. Больше примеров её применения можно найти в документации.