Почему не вызывается финализатор

164
18 мая 2019, 21:00

Объясните, пожалуйста, почему при выполнении данного кода не будет выполнен финализатор?

using System;
namespace Where_Destructor
{
    class Foo : IDisposable
    {
        public String s;
        public Foo()
        {
            Console.WriteLine("I am constructed");
        }
        public void Dispose()
        {
            Console.WriteLine("I am disposed");
        }

        ~Foo()
        {
            Console.WriteLine("I am destructed");                
        }
     }
    class Program
    {
        static void Main(string[] args)
        {
            Foo f;
            using (f = new Foo())
            {
            }
            f = null;
            GC.Collect();
            Console.ReadKey();
        }
    }
}

Результатом будет:

I am constructed I am disposed

Однако, если мы изменим код на такой:

using System;
namespace Where_Destructor
{
    class Foo : IDisposable
    {
        public String s;
        public Foo()
        {
            Console.WriteLine("I am constructed");
        }
        public void Dispose()
        {
            Console.WriteLine("I am disposed");
        }

        ~Foo()
        {
            Console.WriteLine("I am destructed");                
        }
     }
    class Program
    {
        static void Main(string[] args)
        {
            Foo f = new Foo())            
            f = null;
            GC.Collect();
            Console.ReadKey();
        }
    }
}

то финализатор вызывается вполне успешно.

Результат:

I am constructed I am destructed

Answer 1

Финализатор в первом варианте не вызывается по нескольким причинам:

  1. Финализатор не обязательно отрабатывает в момент сборки мусора. Даже наоборот, объект с финализатором продолжит жить после сборки мусора, ради того, чтобы системный поток смог вызвать на нем финализатор.

  2. Вы проверяете ваш код в Debug сборке. В Debug время жизни локальных переменных может продлеваться ради удобства отладки. Дебаггер вполне мог пометить первое значение f как живущее до конца метода.

Если хотите получить гарантированный вызов финализатора - допишите ручное ожидание и проверяйте в Release:

static void Main(string[] args)
{
    Foo f;
    using (f = new Foo())
    {
    }
    f = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.ReadKey();
}
Answer 2

Проверил ваш код на двух версиях фреймворка: 4.7 и 4.0.
Проверил в режимах Debug и Release. Итого 4 варинта.

На обеих версиях фреймворка поведение одинаковое. Оно зависит от дебага/релиза.

В Debug "I am destructed" не выводится (вернее, выводится уже после нажатия на клавишу, после Console.ReadKey).

В Release "I am destructed" выводится сразу.

Вывод: цитата PashaPash

В Debug время жизни локальных переменных может продлеваться ради удобства отладки.

READ ALSO
C# WPF TreeViewItem Как убрать отступ?

C# WPF TreeViewItem Как убрать отступ?

Как убрать отступ у дочерних елементов TreeViewItem?

129
С# адрес объекта в стеке

С# адрес объекта в стеке

Один мой друг спрашивает, возможна ли такая ситуация в C# когда в куче будет храниться адрес объекта (ссылка на объект) в стеке? С учетом того...

141
C# Защита памяти в Assembly.Load()

C# Защита памяти в Assembly.Load()

Я пытаюсь сделать протектор для программ на C#Суть его в том, что защищаемый файл переводится в байты и шифруется

137
Исключение MissingRuntimeArtifactException при использовании .NET Native

Исключение MissingRuntimeArtifactException при использовании .NET Native

У меня при компиляции сборки в Release появляется вот такая ошибка:

185