【问题标题】:why Destructor is not being called for this code为什么没有为此代码调用析构函数
【发布时间】:2018-11-19 09:17:15
【问题描述】:

我也读过articlethis

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

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 个小时,等待 destructorSubscriber。但这从未调用过,代码占用的内存也没有释放。

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

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

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

或者它确实有 List of string 的东西?如果是的话是什么


更新(面向未来的读者)

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

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

并建议尝试

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 在问题评论中提到的原因。

除了上面所说的,GC 只会在一代填满(或由于显式调用)时收集,并且仅创建 1 个对象不会触发它。是的,该对象符合终结条件,但 GC 没有理由收集它。

【问题讨论】:

  • 首先,这是一个终结器,而不是析构函数。当你有一个显式的终结器时,对象会进入终结队列,我相信这意味着它需要一个额外的 GC(我可能错了那部分)。也就是说,终结不是确定性的,这意味着无法保证何时(或是否?)您的终结器会被调用。超出范围不会自动调用您的终结器。如果您想要确定性,请使用 IDisposable 接口/一次性模式。
  • 另请注意,在这种情况下,不需要终结器(或一次性的)。没有要释放的非托管资源。一旦不再从任何地方引用,运行时/GC 线程将处理您的类的清理。这里的问题是您已经明确定义了一个终结器,它是实际上阻止它被收集的一部分(至少在您期望的时间/地点)
  • 除了上面所说的之外,GC 只会在一代填满(或由于显式调用)时收集,并且仅创建 1 个对象不会触发它。是的,该对象有资格进行终结,但 GC 没有理由收集它。
  • @pinkfloydx33:“终结器”和“析构函数”都是可以接受的。 C# 规范称它们为析构函数,CLR 规范称它们为终结器。这种不匹配很可能是由于 C# 早期希望制作出对 C++ 程序员有吸引力的语法。
  • 原始海报:您应该得到的收获是您不需要编写析构函数。编写析构函数在 C# 中几乎从来都不是正确的做法,您需要成为 C# 内存模型的专家才能编写正确的析构函数。如果你认为你需要一个析构函数,你可能错了;为什么你认为你需要一个析构函数?

标签: c# oop memory-management garbage-collection destructor


【解决方案1】:

当代码到达Console.Read() 行时,Subscriber 实例 不再在范围内(我希望它有资格使用垃圾 收藏)。

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

是不是因为,程序是单线程的,而那个线程正在等待 用于 Console.Read() 上的用户输入?

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

CreateSubscriber();
Console.Read();

收件人:

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

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

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

我们稍微总结一下:

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

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

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

引用 Eric Lippert 的 answer

生命周期可以通过多种机制延长,包括 通过 lambda、迭代器块、异步捕获外部变量 方法等等

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

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

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

【讨论】:

  • 内容丰富且发人深省!谢谢你的回答。
猜你喜欢
  • 2011-12-13
  • 1970-01-01
  • 1970-01-01
  • 2020-02-17
  • 1970-01-01
  • 1970-01-01
  • 2021-08-04
  • 2014-03-05
  • 2018-05-24
相关资源
最近更新 更多