Специальный анализ жизненного цикла случая

Предположим, что

void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();
}

В (1) объект bar указывает на право на сбор мусора? Или bar должен выпасть из сферы действия? Имеет ли значение, если GC.Collect вызывается doSomethingWithoutBar?

Это важно знать, есть ли в баре деструктор (С#) или что-то такое напуганное.

Ответ 1

Объекты могут получить право на сбор мусора, как только они уверены, что они больше не будут использоваться. Вполне возможно, что bar будет собираться мусором до того, как переменная выходит за рамки.

Доказательство:

using System;

class Bar
{
    ~Bar() { Console.WriteLine("Finalized!"); }
}

class Program
{
    static void Main(string[] args)
    {
        Bar bar = new Bar();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }
}

Запуск в режиме выпуска (потому что он не собирается в режиме отладки).

Вывод:

Finalized!
Press any key to exit...

Он также работает на ideone, который использует Mono. Результат тот же.

Ответ 2

Из быстрого считывания спецификации это похоже на специфику реализации. Это позволило мусору собирать его, но не требовалось.

Я получаю это из примечания в разделе 10.9 "Автоматическое управление памятью" Спецификация ECMA:

[Примечание. Реализации могут выбрать анализ кода для определения которые ссылки на объект могут быть использованы в будущем. Для экземпляр, если локальная переменная, которая находится в области видимости, является единственной существующей ссылку на объект, но эта локальная переменная никогда не упоминается в любом возможном продолжении исполнения из текущего исполнения точка в процедуре, реализация может (но не требуется) to) обрабатывать объект, который больше не используется. end note]

Акцент на мой.

Ответ 3

Не определяя, какую версию CLR вы имеете в виду, невозможно трудно быть окончательным в отношении поведения, которое вы увидите здесь.

В этом примере гипотетический CLR мог предположить, что верно следующее:

  • Конструктор для Bar ничего не делает
  • Нет инициализированных полей (т.е. нет потенциальных побочных эффектов для построения объектов)

Полностью игнорируйте строку Bar bar = new Bar(); и оптимизируйте ее, поскольку она ничего не делает.

Что касается моей памяти, то в текущих версиях CLR Bar имеет право на сборку мусора в тот момент, когда вы ее создали.

Ответ 4

Марк ответил на вопрос, но вот решение:
void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();

    GC.KeepAlive(bar); // At the point where you no longer need it
}

Ответ 5

Это может произойти. Например, здесь показана демонстрация того, что экземпляр может быть финализирован, пока вы все еще выполняете его конструктор:

class Program
{
    private static int _lifeState;
    private static bool _end;

    private sealed class Schrodinger
    {
        private int _x;

        public Schrodinger()
        {
            //Here I'm using 'this'
            _x = 1;

            //But now I no longer reference 'this'
            _lifeState = 1;

            //Keep busy to provide an opportunity for GC to collect me
            for (int i=0;i<10000; i++)
            {
                var garbage = new char[20000];
            }

            //Did I die before I finished being constructed?
            if (Interlocked.CompareExchange(ref _lifeState, 0, 1) == 2)
            {
                Console.WriteLine("Am I dead or alive?");
                _end = true;
            }
        }

        ~Schrodinger()
        {
            _lifeState = 2;
        }
    }

    static void Main(string[] args)
    {
        //Keep the GC churning away at finalization to demonstrate the case
        Task.Factory.StartNew(() =>
            {
                while (!_end)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            });

        //Keep constructing cats until we find the desired case
        int catCount = 0;
        while (!_end)
        {
            catCount++;
            var cat = new Schrodinger();
            while (_lifeState != 2)
            {
                Thread.Yield();
            }
        }
        Console.WriteLine("{0} cats died in the making of this boundary case", catCount);
        Console.ReadKey();
    }
}

Чтобы это сработало, вам нужно выпустить сборку Release и запустить ее за пределами Visual Studio (так как в противном случае отладчик вставляет код, который предотвращает эффект.) Я тестировал это с помощью VS 2010 для .NET 4.0 x64.

Вы можете настроить итерации в цикле "keep busy", чтобы повлиять на вероятность завершения работы Cat до завершенной конструкции.