惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
T
Troy Hunt's Blog
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
J
Java Code Geeks
Latest news
Latest news
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
Check Point Blog
Recent Announcements
Recent Announcements
F
Fortinet All Blogs
A
Arctic Wolf
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
S
Schneier on Security
Cyberwarzone
Cyberwarzone
N
Netflix TechBlog - Medium
AWS News Blog
AWS News Blog
Scott Helme
Scott Helme
G
GRAHAM CLULEY
SecWiki News
SecWiki News
N
News and Events Feed by Topic
有赞技术团队
有赞技术团队
Google DeepMind News
Google DeepMind News
H
Heimdal Security Blog
GbyAI
GbyAI
C
Cybersecurity and Infrastructure Security Agency CISA
D
Docker
S
Secure Thoughts
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
F
Full Disclosure
L
LINUX DO - 热门话题
Help Net Security
Help Net Security
博客园 - 司徒正美
C
Cisco Blogs
月光博客
月光博客
O
OpenAI News
P
Proofpoint News Feed
Attack and Defense Labs
Attack and Defense Labs
S
Security @ Cisco Blogs
L
Lohrmann on Cybersecurity
宝玉的分享
宝玉的分享
D
DataBreaches.Net
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Source generators в.NET: как они вытеснили рефлексию из стандартной библиотеки
artem · 2026-06-24 · via Все публикации подряд на Хабре

Средний

9 мин

49

Привет, Хабр! Открываете JIT‑логи свежезапущенного ASP.NET Core сервиса и видите, что внушительная часть тиков тратится на построение метаданных через рефлексию: типы пробегаются по GetType, свойства собираются через GetProperties, делегаты компилируются через Expression.Compile.

Это работает давно и для большинства сценариев работает быстро после прогрева. Проблема в том, что в современных условиях прогрев перестал быть допустимой роскошью, и от рефлексии в горячем пути приходится постепенно избавляться.

За последние пять с лишним лет, начиная с.NET 5 и появления source generators в C# 9, стандартная библиотека постепенно переходит на кодогенерацию во время компиляции вместо рефлексии в runtime. К моменту выхода .NET 10 в ноябре 2025 source generators проникли в JSON‑сериализацию, логирование, regex, конфигурацию, минимальные API, EF Core и десяток других мест.

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

Чем source generator отличается от рефлексии

Рефлексия работает во время исполнения программы. Когда JsonSerializer.Serialize(obj) встречает незнакомый тип, runtime обращается к метаданным сборки, читает список свойств, для каждого создаёт делегат‑аксессор, кэширует результат и применяет его к объекту. Первый вызов медленный, последующие быстрые за счёт кэша. Стоимость: время на построение кэша, память на хранение делегатов, невозможность статического анализа.

Source generator работает во время компиляции. Это специальный класс, который реализует интерфейс IIncrementalGenerator, регистрируется в проекте через [Generator] и получает на вход синтаксическое дерево всего проекта. Генератор анализирует код, находит интересующие его конструкции (например, классы с атрибутом [JsonSerializable]) и выдаёт дополнительные C#‑файлы, которые компилируются вместе с исходными. На выходе получается обычный код, без рефлексии, без runtime‑кэшей и без задержки на первый вызов.

Принципиальная разница в том, что рефлексия откладывает работу до момента исполнения и платит за гибкость производительностью, source generator выполняет работу заранее и платит за производительность ограничением: типы должны быть известны во время компиляции. Для подавляющего большинства сценариев в реальном коде это ограничение не критично.

Как System.Text.Json избавился от рефлексии

Сериализация JSON долгие годы оставалась эталонным случаем оправданной рефлексии. Универсальный сериализатор должен уметь работать с произвольными типами, и без чтения метаданных типов в runtime сделать это нельзя. В.NET 6 Microsoft выпустил source‑generated подход к сериализации, и теперь его рекомендуют как основной для всех новых проектов.

Старый подход:

public record Order(int Id, string Customer, decimal Total);

string json = JsonSerializer.Serialize(new Order(1, "Alice", 99.99m));

При первом вызове JsonSerializer.Serialize runtime рефлексией собирает информацию о свойствах Order, строит конвертеры и сериализаторы, всё кэширует в JsonSerializerOptions. Прогретый путь быстрый, холодный старт ощутимый.

Новый подход через source generator:

public record Order(int Id, string Customer, decimal Total);

[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext
{
}

string json = JsonSerializer.Serialize(
    new Order(1, "Alice", 99.99m),
    AppJsonContext.Default.Order
);

При компиляции source generator System.Text.Json находит атрибут [JsonSerializable], генерирует partial‑расширение для AppJsonContext, в котором лежат готовые сериализаторы для Order и всех типов, на которые он ссылается. Никакой рефлексии в runtime, никакой инициализации кэша, никаких задержек на первый вызов. Сгенерированный код можно открыть прямо в IDE: в Visual Studio и Rider есть навигация в сгенерированные файлы через Go to Definition.

На самых базовых сценариях REST API сериализация через source generator проходит на двадцать‑сорок процентов быстрее по холодному пути, потребляет меньше памяти и совместима с Native AOT. Последнее особенно важно: рефлексия в AOT‑режиме либо не работает, либо требует громоздких хинтов в проектном файле, source generator работает без оговорок.

В.NET 10 эта связка стала рекомендуемой по умолчанию для новых проектов, а в шаблонах ASP.NET Core минимальные API теперь генерируют JsonSerializerContext автоматически.

LoggerMessage attribute и производительность логирования

Стандартный код логирования выглядит так:

logger.LogInformation("Processed order {OrderId} for {Customer} with total {Total:C}", 
    order.Id, order.Customer, order.Total);

Под капотом LogInformation принимает массив object[] для аргументов, проверяет уровень логирования, при необходимости боксит value‑типы (order.Id и order.Total), парсит шаблон сообщения, форматирует. Если уровень логирования отключён, бокс всё равно происходит до проверки. Старый совет был обворачивать вызовы в if (logger.IsEnabled(...)), но это засоряло код.

В.NET 6 появился source generator для логирования через атрибут [LoggerMessage]:

public partial class OrderService
{
    private readonly ILogger<OrderService> _logger;

    public OrderService(ILogger<OrderService> logger) => _logger = logger;

    [LoggerMessage(
        EventId = 1001,
        Level = LogLevel.Information,
        Message = "Processed order {OrderId} for {Customer} with total {Total:C}")]
    public partial void LogOrderProcessed(int orderId, string customer, decimal total);

    public void ProcessOrder(Order order)
    {
        LogOrderProcessed(order.Id, order.Customer, order.Total);
    }
}

Source generator видит [LoggerMessage] на partial‑методе и генерирует реализацию: проверка IsEnabled происходит первой, бокса аргументов нет (строго типизированные параметры передаются напрямую), шаблон сообщения парсится во время компиляции, форматирование оптимизировано под конкретный набор аргументов. На горячих путях логирования разница достигает десятков наносекунд на вызов, что в сервисах с миллионами запросов в секунду превращается в проценты CPU.

Дополнительная фича в том, что статический анализатор подсвечивает несоответствие между параметрами шаблона и аргументами метода прямо в IDE. Если в шаблоне {OrderId}, а в параметре order_id (с подчёркиванием), анализатор это поймает на этапе сборки, а не в проде через месяц.

Regex compiles to code

Регулярные выражения в.NET долгое время предлагали выбор: интерпретация или compiled‑режим. Интерпретация быстро стартует, медленно работает на больших объёмах. Compiled генерирует IL во время первого вызова, что даёт ускорение, но добавляет паузу на компиляцию и не работает с AOT.

В.NET 7 появился source generator для regex:

public partial class EmailValidator
{
    [GeneratedRegex(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
    public static partial Regex Email();

    public bool IsValid(string input) => Email().IsMatch(input);
}

Во время компиляции генератор парсит регулярное выражение, строит из него специализированный код на C# и подставляет в partial‑метод. Получается obvious‑код без интерпретатора и без отложенной IL‑компиляции. Стартует мгновенно, работает быстрее compiled‑режима в большинстве случаев, прекрасно работает с Native AOT.

Если регулярка некорректна, ошибка появляется во время сборки с указанием места проблемы в строке шаблона, а не в runtime при первом срабатывании. Это редкая, но приятная мелочь.

Биндинг конфигурации без рефлексии

IConfiguration.Get<MyOptions>() исторически использовал рефлексию для построения биндинга свойств из ключей конфигурации. В.NET 8 появился source generator для конфигурации, в.NET 10 он стал стабильнее и применяется в шаблонах по дефолту.

public sealed class DatabaseOptions
{
    public string ConnectionString { get; init; } = "";
    public int CommandTimeout { get; init; } = 30;
    public bool EnableRetry { get; init; } = true;
}

// В Program.cs
builder.Services.Configure<DatabaseOptions>(
    builder.Configuration.GetSection("Database"));

С включённым EnableConfigurationBindingGenerator в проекте source generator анализирует все вызовы Configure<T>, GetSection().Get<T>() и генерирует код биндинга специально для конкретных типов. Никакой рефлексии, никакой динамической диспетчеризации, полная совместимость с AOT.

Включается это одним флагом в csproj:

<PropertyGroup>
  <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

После этого веб‑приложение стартует на пятнадцать‑двадцать миллисекунд быстрее за счёт устранения работы рефлексии при инициализации опций.

Что нового в.NET 10

В.NET 10 source generators получили два важных обновления.

Первое: новый API AddEmbeddedAttributeDefinition() для авторов генераторов. Раньше при создании source generator с собственным marker‑атрибутом возникала проблема: атрибут нужен и в проекте пользователя для маркировки, и в самом генераторе для распознавания, а делиться сборками между генератором (netstandard2.0) и пользовательским проектом сложно. Стандартное решение состояло в том, чтобы сам генератор добавлял исходник атрибута в компиляцию пользователя. Это работало, но усложняло код. Новый API решает задачу одной строкой и снимает целый пласт boilerplate‑кода в собственных генераторах.

Второе: partial events. C# 14 разрешает разделять объявление и реализацию event так же, как это давно работало для методов и свойств. Это открывает дорогу source generators, которые генерируют реализации event‑ов, что раньше требовало гораздо более громоздкого workaround через partial‑методы. Применений много: автоматические event‑агрегаторы, model‑binding события для UI‑фреймворков, mediator‑паттерны.

В.NET 10 также появился новый тестовый фреймворк TUnit, построенный полностью на source generators. Он обнаруживает тесты во время компиляции, исключая рефлексию из этапа discovery, что критично для AOT‑сценариев и просто заметно ускоряет старт тестового прогона. Для команд, начинающих новые проекты на.NET 10, TUnit становится одной из реальных альтернатив xUnit и NUnit.

Когда писать свой source generator

Большинство разработчиков никогда не напишет свой source generator, и это нормально. Но иногда смысл появляется: повторяющийся boilerplate, который хочется генерировать, маппинг между типами, валидация на основе атрибутов, генерация API‑клиентов из OpenAPI, генерация INPC‑кода для view‑моделей в WPF и MAUI.

Минимальный каскад генератора:

[Generator]
public class MyGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classesWithAttribute = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyLib.MyAttribute",
                predicate: static (node, _) => node is ClassDeclarationSyntax,
                transform: static (ctx, _) => GetClassInfo(ctx))
            .Where(static info => info is not null);

        context.RegisterSourceOutput(classesWithAttribute, (ctx, info) =>
        {
            var source = GenerateSource(info!);
            ctx.AddSource($"{info.ClassName}.g.cs", source);
        });
    }
}

Главные правила: использовать IIncrementalGenerator, а не устаревший ISourceGenerator, потому что инкрементальный API не пересобирает всё дерево при каждом изменении файла.

Когда source generator не подходит

Не всегда замена рефлексии на генератор имеет смысл. Сценарии, где рефлексия остаётся уместной:

  • Плагинные системы с динамической загрузкой сборок: типы становятся известны только в runtime, генератор тут просто не сможет их увидеть.

  • Инструменты интроспекции работающего приложения: профайлеры, отладчики, какие‑нибудь системы динамического конфигурирования.

  • Библиотечный код, который должен работать с произвольными user‑defined типами: например, общий контейнер DI, который принимает любые типы потребителей. Заставлять каждого пользователя писать source generator под свои типы непрактично, поэтому популярные DI‑контейнеры всё равно используют compiled expressions.

  • Маленькие редкие операции, где сложность Roslyn‑генератора не окупается ни производительностью, ни читаемостью кода.


Source generators за пять лет прошли путь от эксперимента до базового инструмента.NET, и в 2026 году это уже не «фича для энтузиастов», а стандарт того, как стандартная библиотека генерирует то, что раньше собирала рефлексией в runtime. JSON, логи, regex, конфигурация: везде кодогенерация во время компиляции стала приоритетной.

Если в проекте до сих пор JsonSerializer.Serialize без контекста и logger.LogInformation без [LoggerMessage], переход на source generators обычно занимает пару дней работы, окупается холодным стартом и снимает целый класс проблем при будущем переходе на Native AOT. Сама рефлексия никуда не делась и не денется, она остаётся правильным инструментом для динамических сценариев и плагинных систем, но для статически известных типов её место уже занято кодогенерацией.

А как у вас в проектах с source generators? Уже мигрировали на JsonSerializerContext и [LoggerMessage], или всё ещё откладываете? Делитесь в комментах, особенно если упирались в нетривиальные ограничения генераторов на сложных типах.

Если хотите увереннее чувствовать себя в C# и лучше понимать, как работают его базовые механизмы на практике, приходите на бесплатные уроки от преподавателей курсов OTUS:

  • 2 июля в 20:00 — «Методы, их перегрузка и расширения». Записаться
    Разберем, как устроены методы в C#, когда использовать перегрузку и чем полезны методы расширения.

  • 16 июля в 20:00 — «Коллекции и структуры данных на C#». Записаться
    Покажем, как правильно выбирать структуры данных и какие решения помогают избежать проблем с производительностью.

Полный список бесплатных уроков июня смотрите в дайджесте.