【发布时间】:2018-11-19 09:17:15
【问题描述】:
我试图用简单的代码实现析构函数。
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 个小时,等待 destructor 的 Subscriber。但这从未调用过,代码占用的内存也没有释放。
我明白,在 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