Lo que falta aquí es que el compilador está extendiendo la vida útil de su x
variable hasta el final del método en el que se define, eso es algo que hace el compilador, pero solo lo hace para una compilación DEPURACIÓN.
Si cambia el código para que la variable se defina en un método separado, funcionará como espera.
La salida del siguiente código es:
False
True
Y el código:
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();
}
}
}
Así que, básicamente, tu comprensión fue correcta, pero no sabías que el furtivo compilador mantendría viva tu variable hasta después de que llamaste GC.Collect()
, ¡incluso si la configuraste explícitamente como nula!
Como señalé anteriormente, esto solo sucede para una compilación DEBUG, presumiblemente para que pueda inspeccionar los valores de las variables locales mientras se depura hasta el final del método (¡pero eso es solo una suposición!).
El código original funciona como se esperaba para una compilación de lanzamiento, por lo que el siguiente código genera false, true
una compilación RELEASE y false, false
una compilación DEBUG:
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.
}
}
}
Como anexo: tenga en cuenta que si hace algo en el finalizador para una clase que hace que una referencia al objeto que se está finalizando sea accesible desde la raíz de un programa, entonces ese objeto NO se recolectará basura a menos que y hasta que ese objeto ya no esté referenciado
En otras palabras, puede darle a un objeto una "suspensión de ejecución" a través del finalizador. Sin embargo, esto generalmente se considera un mal diseño.
Por ejemplo, en el código anterior, donde lo hacemos _extendMyLifetime = this
en el finalizador, estamos creando una nueva referencia al objeto, por lo que ahora no se recolectará basura hasta que _extendMyLifetime
(y cualquier otra referencia) ya no haga referencia a él.
Person1
? Solo veoPerson
. Por último: consulte docs.microsoft.com/dotnet/csharp/programming-guide/… para ver cómo funcionan los finalizadores.