CLR и среда выполнения
Управляемый код, сборка мусора, сборки, рефлексия, сериализация, IL/JIT и LINQ.
11 вопросов
JuniorТеорияОчень частоЧто такое сборщик мусора (GC) в .NET и как он управляет памятью?
Что такое сборщик мусора (GC) в .NET и как он управляет памятью?
Сборщик мусора (GC) автоматически освобождает объекты управляемой кучи, до которых больше нельзя добраться ни от одного корня (локальные, статические переменные, GC-хендлы). Он работает по своему расписанию, поэтому освобождение недетерминировано: вы выделяете объекты, но никогда не освобождаете их вручную, и нельзя предсказать, когда именно вернётся память недостижимого объекта. Это устраняет ошибки висячих указателей и двойного освобождения.
Типичные ошибки
- ✗Ожидать детерминированного освобождения по области видимости как у деструктора C++, а не недетерминированной сборки
- ✗Считать, что .NET использует подсчёт ссылок, из-за чего циклы утекали бы; на деле GC отслеживает достижимость
- ✗Думать, что ручной вызов GC.Collect() — нормальная практика, тогда как обычно он вредит производительности
Уточняющие вопросы
- →Как GC решает, что объект недостижим, а не просто временно не используется?
- →Почему ручной вызов GC.Collect() обычно не рекомендуется в продакшен-коде?
JuniorТеорияЧастоЧто такое сборка (assembly) в .NET и что она содержит?
Что такое сборка (assembly) в .NET и что она содержит?
Сборка (assembly) — это базовая единица развёртывания и версионирования в .NET, упакованная как .dll или .exe. Она содержит скомпилированный IL-код, манифест (идентичность сборки, версию и ссылки на другие сборки), метаданные типов с описанием каждого типа и члена, а также необязательные встроенные ресурсы. CLR загружает и проверяет версии на уровне сборки.
Типичные ошибки
- ✗Путать сборку с пространством имён; пространства имён организуют имена, а сборки — физические единицы развёртывания
- ✗Забывать про манифест, считая сборку лишь скомпилированным IL без идентичности и метаданных ссылок
- ✗Считать, что одна сборка равна одному классу или файлу, тогда как сборка может хранить много типов из разных файлов
Уточняющие вопросы
- →Чем отличается приватная сборка от общей в GAC (Global Assembly Cache)?
- →Как CLR разрешает и находит ссылочную сборку во время загрузки?
JuniorТеорияЧастоЧто такое управляемый код и чем он отличается от неуправляемого?
Что такое управляемый код и чем он отличается от неуправляемого?
Управляемый код выполняется под управлением CLR (Common Language Runtime), который обеспечивает сборку мусора, безопасность типов, обработку исключений и JIT-компиляцию. Неуправляемый код (например, C/C++) компилируется сразу в нативный машинный код и работает напрямую на ОС, сам управляя своей памятью и временем жизни без этих сервисов среды выполнения.
Типичные ошибки
- ✗Считать весь код .NET полностью управляемым, забывая, что нативное взаимодействие выполняется как неуправляемый код вне контроля CLR
- ✗Думать, что управляемость означает лишь сборку мусора, игнорируя безопасность типов, JIT и обработку исключений, которые тоже даёт CLR
- ✗Полагать, что неуправляемый код всегда быстрее, игнорируя, что JIT может оптимизировать под реальный CPU хоста
Уточняющие вопросы
- →Как управляемое приложение безопасно вызывает неуправляемую нативную библиотеку?
- →Что делает CLR (Common Language Runtime) в момент загрузки управляемой сборки?
MiddleТеорияЧастоКак C# проходит путь от исходника до выполнения через IL и JIT-компилятор?
Как C# проходит путь от исходника до выполнения через IL и JIT-компилятор?
Компилятор C# генерирует IL (Intermediate Language) — независимый от CPU байт-код — в сборку вместе с метаданными, а не нативный код. Во время выполнения JIT (Just-In-Time) компилятор переводит IL каждого метода в нативный машинный код лениво, при первом вызове, и кэширует результат, чтобы последующие вызовы выполнялись нативно. Это позволяет одной сборке нацеливаться на любой CPU и оптимизироваться под реальный хост.
Типичные ошибки
- ✗Думать, что компилятор C# сразу даёт нативный код, упуская промежуточный этап IL
- ✗Считать, что вся сборка JIT-компилируется при старте, а не по методу при первом вызове
- ✗Путать IL с интерпретируемым языком, разбираемым каждый вызов, вместо компиляции один раз и кэширования
Уточняющие вопросы
- →Что такое AOT (Ahead-Of-Time) компиляция и чем она отличается от JIT?
- →Как JIT может выдать более быстрый код, чем фиксированная заранее собранная нативная сборка?
MiddleТеорияИногдаЧто такое LINQ (Language Integrated Query) и как он выполняется?
Что такое LINQ (Language Integrated Query) и как он выполняется?
LINQ (Language Integrated Query) — это единый набор операторов запросов и синтаксис запросов, работающие над любой последовательностью, реализующей IEnumerable или IQueryable — коллекции, базы данных, XML. Операторы вроде Where, Select и OrderBy собираются в конвейер. Большинство запросов выполняются лениво (отложенно): ничего не происходит, пока вы не перечислите результат, и каждый элемент проходит конвейер по требованию.
Типичные ошибки
- ✗Считать, что запросы LINQ выполняются жадно, тогда как большинство откладывают выполнение до перечисления
- ✗Забывать, что повторное перечисление отложенного запроса запускает его заново, перечитывая изменившийся источник
- ✗Думать, что LINQ нацелен только на базы, игнорируя LINQ to Objects над любой последовательностью в памяти
Уточняющие вопросы
- →Как
IQueryableпозволяет провайдеру LINQ переводить запрос в SQL вместо выполнения в памяти? - →Какие ловушки создаёт отложенное выполнение, когда источник меняется между перечислениями?
MiddleТеорияИногдаЧто такое рефлексия в .NET и что она позволяет делать во время выполнения?
Что такое рефлексия в .NET и что она позволяет делать во время выполнения?
Рефлексия исследует метаданные типов во время выполнения через System.Reflection и класс Type: можно перечислять типы, члены, атрибуты и сигнатуры любой загруженной сборки. Помимо осмотра, она может действовать — создавать экземпляры типов, читать и задавать поля и свойства, динамически вызывать методы по имени. Это лежит в основе сериализаторов, внедрения зависимостей, ORM и тестовых фреймворков, работающих с типами, неизвестными во время компиляции.
Типичные ошибки
- ✗Считать рефлексию доступной лишь на чтение, забывая, что она создаёт экземпляры и динамически вызывает члены
- ✗Полагать, что рефлексия читает исходные файлы, тогда как она читает скомпилированные метаданные в сборке
- ✗Игнорировать стоимость рефлексии во время выполнения, вызывая её в горячих путях вместо кэширования MemberInfo
Уточняющие вопросы
- →Почему доступ к членам через рефлексию медленнее прямого скомпилированного вызова?
- →Как пользовательские атрибуты в связке с рефлексией управляют сериализаторами и валидаторами?
MiddleТеорияИногдаЧто такое сериализация в .NET, как она работает и зачем она нужна?
Что такое сериализация в .NET, как она работает и зачем она нужна?
Сериализация преобразует граф объектов в памяти в переносимую форму — JSON, XML или бинарную — которую можно сохранить или передать. Десериализация — обратный процесс: она восстанавливает граф объектов из этой формы. Её используют для сохранения состояния на диск или в базу, отправки объектов по сети или через очередь сообщений и кэширования, чтобы один объект пересекал границы процесса, машины или времени.
Типичные ошибки
- ✗Путать сериализацию с сериализацией потоков (последовательным доступом) — это несвязанное понятие
- ✗Забывать, что десериализация может упасть или выполнить недоверенный код, считая входные данные всегда безопасными
- ✗Считать, что сериализуется всё состояние объекта, игнорируя, что поля вроде дескрипторов файлов или событий не восстанавливаются
Уточняющие вопросы
- →Почему бинарные форматы десериализации считаются риском безопасности для недоверенного ввода?
- →Как атрибуты управляют тем, какие члены сериализуются и под какими именами?
SeniorТеорияИногдаЧто такое дерево выражений (expression tree) и как его используют провайдеры LINQ?
Что такое дерево выражений (expression tree) и как его используют провайдеры LINQ?
Дерево выражений (Expression<Func<...>>) представляет код как осматриваемую структуру данных — дерево узлов для операций, параметров и констант — вместо компиляции в исполняемый IL. Так как это данные, провайдер LINQ может обойти и проанализировать его во время выполнения: провайдеры вроде Entity Framework переводят дерево в другой язык, например SQL, а не выполняют лямбду в памяти. Дерево также можно скомпилировать в делегат по требованию.
Типичные ошибки
- ✗Путать
Expression<Func<...>>с обычным делегатомFunc<...>, упуская, что одно — данные, а другое — исполняемое - ✗Думать, что деревья выражений существуют лишь во время компиляции, тогда как это объекты времени выполнения, осматриваемые провайдерами
- ✗Считать, что любая лямбда в памяти переводится в SQL, игнорируя, что это делают лишь деревья выражений (IQueryable)
Уточняющие вопросы
- →Почему
IQueryableиспользует деревья выражений, аIEnumerable— скомпилированные делегаты? - →Какой код C# нельзя представить деревом выражений и почему?
SeniorТеорияИногдаЧто такое финализатор в C# и когда вместо него предпочесть Dispose?
Что такое финализатор в C# и когда вместо него предпочесть Dispose?
Финализатор (~Type()) выполняется недетерминированно в выделенном потоке GC прямо перед освобождением объекта, без гарантий когда и в каком порядке. Поскольку финализируемые объекты переживают лишний цикл сборки (попадают в очередь финализации, собираются позже), они задерживают освобождение и нагружают GC. Предпочитайте IDisposable/Dispose для детерминированной очистки, а финализатор оставляйте лишь как страховку для неуправляемых ресурсов.
Типичные ошибки
- ✗Считать финализатор деструктором C++ с детерминированным, привязанным к области временем на вызывающем потоке
- ✗Добавлять финализаторы повсюду, не осознавая, что они задерживают освобождение на лишний цикл и нагружают GC
- ✗Реализовать финализатор, но забыть GC.SuppressFinalize в Dispose, из-за чего объект всё равно ставится в очередь
Уточняющие вопросы
- →Как паттерн Dispose правильно сочетает финализатор с GC.SuppressFinalize?
- →Почему финализатор может воскресить объект и какие проблемы это вызывает?
SeniorТеорияИногдаПочему сборщик мусора .NET поколенческий и как работают поколения?
Почему сборщик мусора .NET поколенческий и как работают поколения?
GC делит управляемую кучу на поколения 0, 1 и 2. Новые объекты начинают в gen 0; объекты, пережившие сборку, продвигаются в следующее, более старшее поколение. Это использует поколенческую гипотезу — большинство объектов умирают молодыми — поэтому GC собирает gen 0 часто и дёшево, затрагивая лишь недавно выделенную память, а дорогие полные сборки gen 2, сканирующие всё, выполняются гораздо реже.
Типичные ошибки
- ✗Считать, что поколения ранжируют объекты по размеру, а не по числу переживённых сборок
- ✗Думать, что выжившие остаются на месте, упуская, что переживание сборки продвигает объект в старшее поколение
- ✗Полагать, что полные сборки gen 2 — частый дешёвый случай, тогда как частые дешёвые — это сборки gen 0
Уточняющие вопросы
- →Где находится Large Object Heap и почему он собирается вместе с gen 2?
- →Как чрезмерное продвижение объектов в gen 2 может вредить пропускной способности приложения?
MiddleТеорияРедкоЧто такое строгое имя (strong name) сборки и что оно даёт?
Что такое строгое имя (strong name) сборки и что оно даёт?
Строгое имя даёт сборке глобально уникальную идентичность из простого имени, версии, культуры и публичного ключа, плюс цифровую подпись над сборкой. Подпись позволяет CLR обнаруживать подмену и привязываться к точной версии, а уникальная идентичность позволяет установить сборку в GAC (Global Assembly Cache) для общего использования на всей машине без коллизий имён.
Типичные ошибки
- ✗Думать, что строгое имя шифрует сборку, тогда как оно подписывает её ради идентичности и целостности, а не конфиденциальности
- ✗Путать строгое имя с просто уникальным именем файла, упуская версию, публичный ключ и подпись
- ✗Считать, что строгое имя само доказывает доверие к издателю, что на деле требует сертификатов Authenticode
Уточняющие вопросы
- →Чем подпись строгим именем отличается от издательских сертификатов Authenticode?
- →Почему сборка со строгим именем требовала строгого имени и у зависимостей в старых фреймворках?