c#中的垃圾收集误解

本文关键字:误解 | 更新日期: 2023-09-27 18:27:22

我在谷歌上搜索过,没有得到我想要的。我不知道我是对是错。听着,我试着理解GC.Collect(),下面是代码。。

public class SomePublisher
{
    public event EventHandler SomeEvent;
}
public class SomeSubscriber
{
    public static int Count;
    public SomeSubscriber(SomePublisher publisher)
    {
        publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
    }
    ~SomeSubscriber()
    {
        SomeSubscriber.Count++;
    }
    private void publisher_SomeEvent(object sender, EventArgs e)
    {
        // TODO: something
    }
}

我在主线中这样做。。

 SomePublisher publisher = new SomePublisher();
        for (int i = 0; i < 10; i++)
        {
            SomeSubscriber subscriber = new SomeSubscriber(publisher);
            subscriber = null;
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(SomeSubscriber.Count.ToString());
        Console.ReadLine();

我得到的输出是0,但根据我的说法应该是10,因为GC.Collect()必须从内存中移除class1对象,所以必须调用class1析构函数,所以计数必须增加到10。任何人都能解释这一点吗。。

c#中的垃圾收集误解

(还要注意,C#没有析构函数1,它有终结器。这些是非常不同的东西,你不应该混淆它们。)

"问题"就在这一行:

publisher.SomeEvent += new EventHandler(publisher_SomeEvent);

这将创建一个以特定对象实例的publisher_SomeEvent()方法为目标的委托,并将此委托添加到publisher.SomeEvent事件的调用列表中。此委托对象引用目标对象,并且它正在阻止收集该对象!(这是一件好事——如果你把一个委托带到一个特定对象上的方法,那么你不希望该对象被收集,直到该委托不再被引用。)

从技术上讲,这根本不是问题,而是运行时保持仍被引用的对象的活动性。

举例来说,这是参考链:

SomePublisher -+-> EventHandler --> SomeSubscriber
               |
               +-> EventHandler --> SomeSubscriber
               |
               +-> (Eight more...)

在调用GC.Collect():之前,您需要做以下两件事之一

  1. 在释放每个SomePublisher对象之前取消订阅该事件。这将使EventHandler委托实例和它们引用的SomeSubscriber实例都有资格进行收集
  2. 设置publisher = null;。这将导致整个对象图都有资格进行收集

在这两种情况下,这将释放对SomeSubscriber对象的所有引用。


1注意,C#规范确实将这些代码块称为"析构函数",但这是一个可怕的名称。熟悉垃圾收集语言的人会对此感到困惑,因为"终结器"是垃圾收集器在对象不再可访问时调用的代码的广泛术语。C++开发人员尤其希望析构函数在不同的时间执行。是的,C#有一个叫做"析构函数"的东西,但它不是析构函数。(说些什么并不意味着这样!)

SomeSubcriber对象的终结器直到结束才会被调用。因为即使SomeSubscriber对象设置为null,它的内存仍被SomePublisher对象的事件SomeEvent引用,该事件在应用程序结束前一直有效。因此,当调用GC.Collect()时,垃圾收集器将找不到任何要放入终结器队列的对象。

public SomeSubscriber(SomePublisher publisher)
{
// publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
}

如果我们可以用上面的代码替换SomeSubcriber的构造函数,那么我们将在控制台中得到10的结果。