Вы тут спотыкались и делаете очень неправильные выводы, потому что используете отладчик. Вам нужно будет запустить свой код так, как он работает на компьютере пользователя. Перейдите в сборку Release сначала с помощью менеджера сборки + Configuration, измените комбинацию «Active solution configuration» в левом верхнем углу, чтобы «Отпустить». Затем перейдите в Инструменты + Параметры, Отладка, Общие и отключите опцию «Подавить оптимизацию JIT».
Теперь запустите свою программу снова и поработайте с исходным кодом. Обратите внимание, что дополнительные фигурные скобки не имеют никакого эффекта. И обратите внимание, что установка переменной в значение null не имеет никакого значения. Он всегда будет печатать «1». Теперь он работает так, как вы надеетесь, и ожидал, что это сработает.
Который уходит с задачей объяснить, почему он работает так по-разному, когда вы запускаете сборку Debug. Это требует объяснения того, как сборщик мусора обнаруживает локальные переменные и как это влияет на наличие отладчика.
Во-первых, джиттер выполняет две важные обязанности, когда компилирует IL для метода в машинный код. Первый из них очень заметен в отладчике, вы можете увидеть машинный код с окном Debug + Windows + Disassembly. Вторая обязанность, однако, полностью невидима. Он также генерирует таблицу, которая описывает, как используются локальные переменные внутри тела метода. В этой таблице есть запись для каждого аргумента метода и локальной переменной с двумя адресами. Адрес, в котором переменная сначала сохранит ссылку на объект. И адрес инструкции машинного кода, где эта переменная больше не используется. Также сохраняется ли эта переменная в кадре стека или в регистре процессора.
Эта таблица важна для сборщика мусора, она должна знать, где искать ссылки на объекты, когда она выполняет сбор. Довольно легко сделать, когда ссылка является частью объекта в куче GC. Определенно нелегко сделать, когда ссылка на объект хранится в регистре CPU. В таблице говорится, где искать.
«Больше не использованный» адрес в таблице очень важен. Это делает сборщик мусора очень эффективным . Он может собирать ссылку на объект, даже если он используется внутри метода и этот метод еще не завершил выполнение. Что очень часто, ваш метод Main (), например, будет прекращать выполнение только до завершения вашей программы. Очевидно, что вам не нужны ссылки на объекты, используемые внутри этого метода Main (), чтобы жить в течение всего времени программы, что будет означать утечку. Джиттер может использовать таблицу, чтобы обнаружить, что такая локальная переменная больше не полезна, в зависимости от того, насколько далеко продвинулась программа внутри этого метода Main () до совершения вызова.
Почти магический метод, связанный с этой таблицей, - GC.KeepAlive (). Это очень специальный метод, он вообще не генерирует никакого кода. Его единственная обязанность - изменить эту таблицу. Он продлевает время жизни локальной переменной, предотвращая хранение ссылки, полученной от сбора мусора. Единственный раз, когда вам нужно использовать его, - это остановить GC от чрезмерного стремления к сбору ссылки, которая может произойти в сценариях взаимодействия, где ссылка передается на неуправляемый код. Сборщик мусора не может видеть, что такие ссылки используются таким кодом, поскольку он не был скомпилирован джиттером, поэтому нет таблицы, в которой говорится, где искать ссылку. Передача объекта делегата неуправляемой функции, такой как EnumWindows (), является примером шаблона, когда вам нужно использовать GC.KeepAlive ().
Итак, как вы можете сказать из вашего образца фрагмента после запуска его в сборке Release, локальные переменные могут быть собраны раньше, прежде чем закончить выполнение метода. Еще более мощно, объект может быть собран, пока один из его методов работает, если этот метод больше не ссылается на это . Существует проблема с этим, очень неудобно отлаживать такой метод. Так как вы можете поместить переменную в окно «Смотреть» или проверить ее. И он исчезнет, когда вы будете отлаживаться, если произойдет GC. Это было бы очень неприятно, поэтому дрожание знает, что есть отладчик. Затем он изменяет таблицу и изменяет «последний использованный» адрес. И изменяет его от нормального значения на адрес последней инструкции в методе. Который сохраняет переменную до тех пор, пока метод не вернулся. Это позволяет вам следить за ним до тех пор, пока метод не вернется.
Это теперь также объясняет, что вы видели раньше, и почему вы задали этот вопрос. Он печатает «0», потому что вызов GC.Collect не может собрать ссылку. В таблице указано, что переменная используется после вызова GC.Collect (), вплоть до конца метода. Вынужден сказать об этом, подключив отладчик и запустив сборку Debug.
Установка переменной в значение null имеет эффект теперь, потому что GC проверит переменную и больше не увидит ссылку. Но убедитесь, что вы не попадаете в ловушку, в которую попали многие программисты на C #, на самом деле написав этот код, было бессмысленно. Не имеет значения, присутствует ли этот оператор при запуске кода в сборке Release. Фактически, оптимизатор джиттера удалит это утверждение, поскольку он не имеет никакого эффекта. Поэтому не забудьте написать такой код, хотя это, казалось, имело эффект.
Одна заключительная заметка об этой теме - вот что заставляет программистов в беде писать небольшие программы, чтобы что-то делать с приложением Office. Отладчик обычно получает их на Неправильный путь, они хотят, чтобы программа Office выходила по требованию. Соответствующий способ сделать это - вызвать GC.Collect (). Но они обнаружат, что это не сработает, когда они отлаживают свое приложение, приводя их в никогда не землю, вызывая Marshal.ReleaseComObject (). Ручное управление памятью, оно редко работает должным образом, потому что они легко упускают из виду невидимую ссылку на интерфейс. GC.Collect () действительно работает, просто не при отладке приложения.