C#MiddleExperience

Какие особенности 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

Related topics