Ви тут стикаєтесь і робите дуже неправильні висновки, оскільки використовуєте відладчик. Вам потрібно буде запустити свій код так, як він працює на машині вашого користувача. Перейдіть до версії версії спочатку за допомогою менеджера збірки + конфігурація, змініть комбінацію "Конфігурація активного рішення" у верхньому лівому куті на "Випуск". Далі перейдіть до Інструменти + Параметри, Налагодження, Загальне та зніміть прапорець "Придушити оптимізацію JIT".
Тепер запустіть програму ще раз і повозиться з вихідним кодом. Зверніть увагу, як додаткові брекети взагалі не мають ефекту. І зверніть увагу, що встановлення змінної на null взагалі не має ніякої різниці. Це завжди буде друкувати "1". Тепер він працює так, як ви сподіваєтесь, і сподівався, що буде працювати.
Що не залишає перед собою завдання пояснити, чому це працює так інакше, коли ви запускаєте збірку налагодження. Це вимагає пояснення того, як збирач сміття виявляє локальні змінні та як на це впливає наявність налагоджувача.
По-перше, тремтіння виконує два важливі завдання, коли збирає ІЛ для методу в машинний код. Перший дуже добре видно на відладчику, машинний код ви можете побачити за допомогою вікна Debug + Windows + Disassembly. Однак другий обов'язок є абсолютно невидимим. Він також створює таблицю, яка описує, як використовуються локальні змінні всередині методу. Ця таблиця містить запис для кожного аргументу методу та локальну змінну з двома адресами. Адреса, де змінна спочатку зберігатиме посилання на об'єкт. І адреса інструкції машинного коду, де ця змінна вже не використовується. Також, чи зберігається ця змінна у фреймі стека чи в регістрі процесора.
Ця таблиця є важливою для збирача сміття, вона повинна знати, де шукати посилання на об'єкти, коли він виконує збір. Це досить легко зробити, коли посилання є частиною об'єкта на купі GC. Безумовно, це не просто зробити, коли посилання на об'єкт зберігається в регістрі процесора. У таблиці вказано, де шукати.
Адреса "більше не використовується" в таблиці дуже важлива. Це робить сміттєзбірник дуже ефективним . Він може збирати посилання на об'єкт, навіть якщо він використовується всередині методу, і цей метод ще не завершив виконання. Що є дуже поширеним, наприклад, ваш Main () метод, коли-небудь припинить виконання лише до завершення програми. Зрозуміло, ви б не хотіли, щоб жодні посилання на об'єкти, використовувані всередині цього методу Main (), існували протягом тривалості програми, що може означати витік. Джиттер може скористатися таблицею, щоб виявити, що така локальна змінна вже не корисна, залежно від того, наскільки програма просунулася всередині цього методу Main () перед тим, як здійснити виклик.
Майже магічний метод, пов’язаний із цією таблицею, - GC.KeepAlive (). Це дуже особливий метод, він взагалі не генерує жодного коду. Єдиний її обов'язок - це змінити цю таблицю. Він поширюєтьсятермін експлуатації локальної змінної, що запобігає зібранню сміття. Єдиний раз, коли вам потрібно це використовувати, - це не допустити перенапруги GC до збору посилання, що може статися в сценаріях інтероп, коли посилання передається на некерований код. Колекціонер сміття не може бачити, як такі посилання використовуються таким кодом, оскільки він не був складений тремтінням, тому немає таблиці, де сказано, де шукати посилання. Передача об'єкта-делегата некерованій функції на зразок EnumWindows () - це приклад використання котла, коли потрібно використовувати GC.KeepAlive ().
Отже, як ви можете сказати зі свого фрагмента зразка після запуску його у збірці випуску, локальні змінні можуть бути зібрані достроково, перш ніж метод завершиться виконанням. Ще сильніше об'єкт може бути зібраний під час запуску одного з його методів, якщо цей метод більше не посилається на це . У цьому є проблема, налагодити такий метод дуже незручно. Оскільки ви цілком можете помістити змінну у вікно Watch або переглянути її. І він би зникав під час налагодження, якщо виникає GC. Це було б дуже неприємно, тому тремтіння знає, що там додається налагоджувач. Потім він модифікуєтьсятаблиця та змінює "останньо використану" адресу. І змінює його зі свого звичайного значення на адресу останньої інструкції методу. Що підтримує змінну живою, поки метод не повернувся. Що дозволяє продовжувати спостерігати, поки метод не повернеться.
Це також пояснює те, що ви бачили раніше, і чому ви задали це питання. Він друкує "0", оскільки виклик GC.Collect не може зібрати посилання. У таблиці говорить , що змінне використовуються останнім виклику GC.Collect (), аж до кінця цього методу. Змусив це сказати, додавши налагоджувач і запустивши збірку налагодження.
Встановлення змінної в null зараз має ефект, оскільки GC буде перевіряти змінну і більше не побачить посилання. Але переконайтеся, що ви не потрапите в пастку, в яку потрапило багато програмістів на C #, адже писати цей код було безглуздо. Не має жодної різниці, чи існує цей випадок під час запуску коду у версії Release. Насправді оптимізатор тремтіння видалить це твердження, оскільки воно не має жодного ефекту. Тому не забудьте писати такий код, навіть якщо це, здавалося, має ефект.
Останнє зауваження щодо цієї теми - це те, що програмістам стає проблем, які пишуть невеликі програми, щоб зробити щось із додатком Office. Налагоджувач зазвичай отримує їх на неправильному шляху, вони хочуть, щоб програма Office виходила на вимогу. Відповідний спосіб зробити це - зателефонувавши GC.Collect (). Але вони виявлять, що це не спрацьовує, коли вони налагоджують свою програму, приводячи їх у ніколи не приземляючись, зателефонувавши Marshal.ReleaseComObject (). Ручне управління пам’яттю, воно рідко працює належним чином, оскільки вони легко не помітять невидиму посилання на інтерфейс. GC.Collect () насправді працює, лише не під час налагодження програми.