析构函数的垃圾收集器行为
Garbage Collector Behavior for Destructor
我有一个简单的 class 定义如下。
public class Person
{
public Person()
{
}
public override string ToString()
{
return "I Still Exist!";
}
~Person()
{
p = this;
}
public static Person p;
}
在 Main 方法中
public static void Main(string[] args)
{
var x = new Person();
x = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Person.p == null);
}
垃圾收集器是否应该作为 Person.p 的主要引用以及何时调用析构函数?
你在这里遗漏的是编译器将你的 x
变量的生命周期延长到定义它的方法结束 - 这只是编译器所做的事情 - 但它只对 DEBUG 构建有效。
如果您更改代码以便在单独的方法中定义变量,它将按您预期的那样工作。
以下代码的输出为:
False
True
代码:
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();
}
}
}
所以基本上您的理解是正确的,但是您不知道偷偷摸摸的编译器会让您的变量保持活动状态,直到 在您调用 GC.Collect()
之后 - 即使如果您明确将其设置为空!
正如我上面提到的,这只发生在 DEBUG 构建中 - 大概是这样您可以在调试到方法结束时检查局部变量的值(但这只是一个猜测!)。
原始代码在发布版本中确实按预期工作 - 因此以下代码输出 false, true
用于 RELEASE 版本和 false, false
用于 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.
}
}
}
作为附录:请注意,如果您在 class 的终结器中执行某些操作,导致对正在终结的对象的引用可以从程序根访问,那么该对象将不是垃圾-收集,除非并直到不再引用该对象。
换句话说,你可以通过终结器给一个对象一个"stay of execution"。不过,这通常被认为是一个糟糕的设计!
例如,在上面的代码中,我们在终结器中执行 _extendMyLifetime = this
的地方,我们正在创建一个对该对象的新引用,因此在 _extendMyLifetime
之前它不会被垃圾回收(和任何其他参考)不再引用它。
我有一个简单的 class 定义如下。
public class Person
{
public Person()
{
}
public override string ToString()
{
return "I Still Exist!";
}
~Person()
{
p = this;
}
public static Person p;
}
在 Main 方法中
public static void Main(string[] args)
{
var x = new Person();
x = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Person.p == null);
}
垃圾收集器是否应该作为 Person.p 的主要引用以及何时调用析构函数?
你在这里遗漏的是编译器将你的 x
变量的生命周期延长到定义它的方法结束 - 这只是编译器所做的事情 - 但它只对 DEBUG 构建有效。
如果您更改代码以便在单独的方法中定义变量,它将按您预期的那样工作。
以下代码的输出为:
False
True
代码:
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();
}
}
}
所以基本上您的理解是正确的,但是您不知道偷偷摸摸的编译器会让您的变量保持活动状态,直到 在您调用 GC.Collect()
之后 - 即使如果您明确将其设置为空!
正如我上面提到的,这只发生在 DEBUG 构建中 - 大概是这样您可以在调试到方法结束时检查局部变量的值(但这只是一个猜测!)。
原始代码在发布版本中确实按预期工作 - 因此以下代码输出 false, true
用于 RELEASE 版本和 false, false
用于 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.
}
}
}
作为附录:请注意,如果您在 class 的终结器中执行某些操作,导致对正在终结的对象的引用可以从程序根访问,那么该对象将不是垃圾-收集,除非并直到不再引用该对象。
换句话说,你可以通过终结器给一个对象一个"stay of execution"。不过,这通常被认为是一个糟糕的设计!
例如,在上面的代码中,我们在终结器中执行 _extendMyLifetime = this
的地方,我们正在创建一个对该对象的新引用,因此在 _extendMyLifetime
之前它不会被垃圾回收(和任何其他参考)不再引用它。