The thing you are missing here is that the compiler is extending the lifetime of your x variable until the end of the method in which it is defined - that's just something the compiler does - but it only does it for a DEBUG build.
If you change the code so that the variable is defined in a separate method, it will work as you expect.
The output of the following code is:
False
True
And the code:
using System;
namespace ConsoleApp1
{
class Finalizable
{
~Finalizable()
{
_extendMyLifetime = this;
}
public static bool LifetimeExtended => _extendMyLifetime != null;
static Finalizable _extendMyLifetime;
}
class Program
{
public static void Main()
{
test();
Console.WriteLine(Finalizable.LifetimeExtended); // False.
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Finalizable.LifetimeExtended); // True.
}
static void test()
{
new Finalizable();
}
}
}
So basically your understanding was correct, but you didn't know that the sneaky compiler was going to keep your variable alive until after you called GC.Collect() - even if you explicitly set it to null!
As I noted above, this only happens for a DEBUG build - presumably so you can inspect the values for local variables while debugging to the end of the method (but that's just a guess!).
The original code DOES work as expected for a release build - so the following code outputs false, true for a RELEASE build and false, false for a DEBUG build:
using System;
namespace ConsoleApp1
{
class Finalizable
{
~Finalizable()
{
_extendMyLifetime = this;
}
public static bool LifetimeExtended => _extendMyLifetime != null;
static Finalizable _extendMyLifetime;
}
class Program
{
public static void Main()
{
new Finalizable();
Console.WriteLine(Finalizable.LifetimeExtended); // False.
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Finalizable.LifetimeExtended); // True iff RELEASE build.
}
}
}
As an addendum: Note that if you do something in the finalizer for a class that causes a reference to the object being finalized to be reachable from a program root, then that object will NOT be garbage-collected unless and until that object is no longer referenced.
In other words, you can give an object a "stay of execution" via the finalizer. This is generally held to be a bad design, though!
For example, in the code above, where we do _extendMyLifetime = this in the finalizer, we are creating a new reference to the object, so it will not now be garbage-collected until _extendMyLifetime (and any other reference) no longer references it.