Какие особенности runtime, type system или memory model C# реально влияют на архитектуру приложения?
Ключевые особенности CLR, влияющие на архитектуру: трёхпоколенческий GC (долгоживущие объекты = GC-паузы), value/reference type семантика (boxing в горячих путях), async/await (thread pool starvation при смешивании с блокирующим кодом) и SynchronizationContext (дедлок в UI-фреймворках).
Особенности CLR, type system и memory model, влияющие на архитектуру
Управляемая память и GC
Трёхпоколенческий GC означает, что долгоживущие объекты в Gen 2 собираются с паузами. Это влияет на архитектурные решения:
- Избегайте крупных object graphs в Singleton-сервисах — они никогда не покидают Gen 2 и давят на GC.
- Для высоконагруженных путей (парсинг, сетевые протоколы) используйте
Span<T>,ArrayPool<T>,MemoryPool<T>чтобы не создавать давление на heap. - Server GC (по умолчанию в ASP.NET Core) создаёт по одной куче на ядро — полезно для throughput, но увеличивает footprint памяти в Docker-контейнерах с малым RAM.
Value types vs Reference types
Выбор между struct и class влияет на аллокации, boxing и семантику копирования:
struct— на стеке или инлайн в родительском объекте; нет GC-overhead, но копируется при передаче. Плохой выбор для больших объектов (>16 байт безreadonly struct).record struct(C# 10+) даёт value equality без boxing — хорош для DTO, value objects в DDD.- Boxing (передача struct через интерфейс) скрыт и незаметен без профайлера — архитектура должна минимизировать его в горячих путях.
Null safety и nullable reference types
Включение <Nullable>enable</Nullable> в csproj переводит систему типов в режим аннотации nullable. Это архитектурно влияет на API boundaries: все публичные методы должны явно объявить, может ли аргумент/результат быть null — это снижает NullReferenceException в production.
Async и thread pool
Async/await освобождает потоки во время I/O, что позволяет ASP.NET Core обслуживать тысячи запросов небольшим пулом. Архитектурное следствие: смешивание блокирующих вызовов (.Result, Thread.Sleep) с async приводит к thread pool starvation под нагрузкой.
SynchronizationContext
В WPF/WinForms/Blazor Server SynchronizationContext направляет продолжения async на UI-поток. Это означает, что в библиотечном коде нужен ConfigureAwait(false), иначе при вызове из UI-приложения возможен дедлок.
Подводные камни
- Непонимание GC-поколений приводит к тому, что кеши хранят объекты в Singleton, разрастаются и вызывают полные GC.
- Смешивание sync и async API (deadlock через .Result) — самая распространённая production-катастрофа.
- Игнорирование nullable annotations порождает NRE в неожиданных местах при изменении контрактов.
- Неправильный lifetime DI (Singleton, захвативший Scoped DbContext) — следствие непонимания lifecycle managed объектов.
What hurts your answer
- Знать термины C#, но не понимать связи между абстракциями
- Объяснять поведение через отдельные примеры вместо причинной модели
- Не связывать mental model с диагностикой ошибок
What they're listening for
- Понимает ключевые абстракции C#
- Может предсказывать поведение системы через mental model
- Связывает модель с debugging и production decisions