为什么不为此代码调用析构函数

why Destructor is not being called for this code

我也读过这个article and this

我试图用简单的代码实现析构函数。

class Program
{
    static void Main(string[] args)
    {
        CreateSubscriber();
        Console.Read();
    }

    static void CreateSubscriber()
    {
        Subscriber s = new Subscriber();
        s.list.Clear();
    }
}

public class Subscriber
{
    public List<string> list = new List<string>();
    public Subscriber()
    {
        for(long i = 0; i < 1000000; i++)
        {
            list.Add(i.ToString());
        }
    }

    ~Subscriber()
    {
        //this line is only performed on explicit GC.Collect()
        Console.WriteLine("Destructor Called - Sub");
    }
}

当代码到达 Console.Read() 行时,Subscriber 的实例不再在范围内(我希望它有资格进行垃圾收集)。我离开上面的代码 运行 将近 2 个小时等待 Subscriberdestructor。但从未调用过,也没有释放代码占用的内存。

我明白了,在 c# 中我们不能以编程方式调用析构函数,它会在 Garbage collection 上自动调用,所以我尝试显式调用 GC.Collect()

通过这样做,我可以看到调用了析构函数。所以在我上面的代码中,垃圾收集没有完成!但为什么?

是否因为程序是单线程的并且该线程正在等待 Console.Read() 上的用户输入?

或者它确实有字符串列表?如果是的话是什么


更新(供未来的读者使用)

正如 Fabjan 在他的回答中所建议的那样

Most likely somewhere when a new object is created and memory is allocated for it, GC does perform a check of all references and collects the first object.

并建议尝试

CreateSubscriber();
Console.Read();
CreateSubscriber();
Console.Readkey();

我更新了如下代码,

class Program
{
    static void Main(string[] args)
    {
        CreateSubscriber(true);
        Console.ReadLine();
        CreateSubscriber(false);
        Console.ReadLine();
    }
    static void CreateSubscriber(bool toDo)
    {
        Subscriber s = new Subscriber(toDo);
        s.list.Clear();
    }
}
public class Subscriber
{
    public List<string> list = new List<string>();
    public Subscriber(bool toDo)
    {
        Console.WriteLine("In Consutructor");
        if (toDo)
        {
            for (long i = 0; i < 5000000; i++)
                list.Add(i.ToString());
        }
        else
        {
            for (long i = 0; i < 2000000; i++)
                list.Add(i.ToString());
        }
        Console.WriteLine("Out Consutructor");
    }
    ~Subscriber()
    {
        Console.WriteLine("Destructor Called - Sub");
    }
}

输出:

正如他所料,在创建 Subscriber 的第二个实例时,我可以看到正在收集 GC(正在调用终结器)。

请注意:在订阅者的构造函数的 else 条件下,我在列表中添加较少的项目,然后在 if 条件下 - 注意应用程序的 RAM 使用量是否相应减少,是的,它也在减少。

在其他情况下,我可以将字符串列表留空(因此内存使用量将显着减少)。但是这样做,GC 并没有被收集。很可能是因为 M.Aroosi 在问题评论中提到的原因。

in addition to what's said above, the GC will only collect once a generation gets full(or due to an explicit call), and just 1 object created wouldn't trigger it. Yes the object is elligible for finalization, but there's no reason for the GC to collect it.

As when code reached line of Console.Read(), instance of Subscriber was no longer in scope (I was expecting it to be eligible for Garbage collection).

当 GC 检测到 Subscriber 实例的引用丢失(超出范围)时,它会将此对象标记为在下一轮中收集。 但是只有 GC 知道下一轮的确切时间

Is it because, program is single threaded and that thread is waiting for user input on Console.Read() ?

不,如果我们 运行 将此代码放在单独的线程中,结果将是相同的。 然而,如果我们改变这个:

CreateSubscriber();
Console.Read();

收件人:

CreateSubscriber();
Console.Read();
CreateSubscriber();
Console.Readkey();

我们可以看到 GC 将在 Console.Read() 之后收集垃圾和 运行 终结器。为什么?

很可能在创建新对象并为其分配内存时,GC 会检查所有引用并收集第一个对象。

我们总结一下:

  • 当我们只创建一个对象,代码中没有引用指向 到这个对象或其 class 直到程序结束 - GC 允许程序结束并且 退出前收集垃圾。

  • 当我们创建一个对象并且某种对 obj 的引用 或其 class - GC 执行检查并收集垃圾。

GC 运行 收集的方式和时间以及对象生命周期结束的方式和时间背后有一些复杂的逻辑。

引自 Eric Lippert 的

The lifetime may be extended by a variety of mechanisms, including capturing outer variables by a lambda, iterator blocks, asynchronous methods, and so on

我们很少需要在对象的销毁 上执行一些代码。在那种特定情况下,我们可以 运行 GC.Collect 显式 何时 obj 将被销毁,而不是猜测。

更常见的是,我们可能需要释放一些托管资源,为此我们可以使用 IDisposable 接口和 using 语句,它们会在控制流离开之前自动调用 Dispose代码块(它将创建一个 try {} finally {} 子句,最后它将为我们调用 Dispose)。

using(myDisposable) 
{ 
   ... 
}  // dispose is called here