Переопределение Object.Finalize () и GC.Collect () - PullRequest
0 голосов
/ 11 ноября 2009

Я не могу понять поведение GC.Collect () в присутствии класса, переопределяющего Object.Finalize (). Это мой базовый код:

namespace test
{
 class Foo
 {
  ~Foo() { Console.WriteLine("Inside Foo.Finalize()"); }
 }

 static class Program
 {

  static void Main()
  {
   {
    Foo bar = new Foo();
   }

   GC.Collect();
   GC.WaitForPendingFinalizers();

   Console.ReadLine();
  }
 }

}

Вопреки тому, что я ожидал, я получаю вывод консоли только при завершении программы, а не после GC.WaitForPendingFinalizers()

Ответы [ 5 ]

12 голосов
/ 11 ноября 2009

Ни компилятор, ни среда выполнения не обязаны гарантировать, что для локальных компьютеров, находящихся вне области действия, время жизни их содержимого будет сокращено. Для компилятора или среды выполнения вполне законно рассматривать это так, как если бы в целях продолжительности вычислений не было фигурных скобок. Если вам требуется очистка на основе фигурных скобок, то внедрите IDisposable и используйте блок «using».

UPDATE:

Относительно вашего вопроса "почему это отличается в оптимизированных и неоптимизированных сборках", хорошо, посмотрите на разницу в codegen.

неоптимизированному:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       28 (0x1c)
  .maxstack  1
  .locals init (class test.Foo V_0)
  IL_0000:  nop
  IL_0001:  nop
  IL_0002:  newobj     instance void test.Foo::.ctor()
  IL_0007:  stloc.0
  IL_0008:  nop
  IL_0009:  call       void [mscorlib]System.GC::Collect()
  IL_000e:  nop
  IL_000f:  call       void [mscorlib]System.GC::WaitForPendingFinalizers()
  IL_0014:  nop
  IL_0015:  call       string [mscorlib]System.Console::ReadLine()
  IL_001a:  pop
  IL_001b:  ret
} // end of method Program::Main

Оптимизированный:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       23 (0x17)
  .maxstack  8
  IL_0000:  newobj     instance void test.Foo::.ctor()
  IL_0005:  pop
  IL_0006:  call       void [mscorlib]System.GC::Collect()
  IL_000b:  call       void [mscorlib]System.GC::WaitForPendingFinalizers()
  IL_0010:  call       string [mscorlib]System.Console::ReadLine()
  IL_0015:  pop
  IL_0016:  ret
} // end of method Program::Main

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

3 голосов
/ 11 ноября 2009

Сборщик мусора не дает абсолютно никаких гарантий относительно того, когда будут собираться данные. Это одна из причин, по которой вам нужно использовать оператор using для утилизации одноразовых предметов.

GC.WaitForPendingFinalizers () ожидает только тех финализаторов, которые были собраны - если объект еще не был собран, он ничего не делает.

Скорее всего, компилятор держит указатель на строку, даже если у вас больше нет доступа к имени.

Я бы попытался поместить вызов new Foo () в отдельную функцию - это могло бы помочь, хотя опять же - без гарантии.

2 голосов
/ 11 ноября 2009
Бар

все еще находится в области действия, когда вы звоните GC.Collect() и GC.WaitForPendingFinalizers()

Foo также не реализует IDisposable().

Я предполагаю, что GC еще не готов освободить память, используемую вашим объектом Foo, и вы не можете явно вызвать Dispose(). Поэтому он удаляется, когда приложение завершает выполнение.

0 голосов
/ 19 сентября 2010

Вот еще одна замечательная статья о GC, которая может появиться в неожиданной точке выполнения кода:

Lifetime, GC.KeepAlive, переработка отходов - cbrumme http://blogs.msdn.com/b/cbrumme/archive/2003/04/19/51365.aspx?wa=wsignin1.0

Мой вопрос: как я могу воспроизвести принудительный сбор мусора в точке, упомянутой в статье? Я пытался поместить GC.Collect () в начале OperateOnHandle () и определил деструктор для класса C, но, похоже, не работает. Деструктор вызывается всегда в конце программы.

0 голосов
/ 11 ноября 2009

Не думаю, что область действия работает так же, как в C ++. Я думаю, что переменные действительно действительны до выхода из функции, например:

class Program
{
    class Foo
    {
        ~Foo() { Console.WriteLine("Test"); }
    }


    static void Test()
    {
        Foo foo = new Foo();
    }

    static void Main()
    {
        Test();

        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.ReadLine();
    }
}

Если вы думаете о IL, то в IL нет такого понятия, как фигурная скобка, и локальные переменные всегда имеют по крайней мере область действия функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...