我有这段代码
public class Publisher
{
public event EventHandler SomeEvent;
}
public class Subscriber
{
public static int Count;
public Subscriber(Publisher publisher)
{
publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
}
~Subscriber()
{
Subscriber.Count++;
}
private void publisher_SomeEvent(object sender, EventArgs e)
{
// TODO
}
}
在我的应用程序的主要方法中,我有
static void Main(string[] args)
{
Publisher publisher = new Publisher();
for (int i = 0; i < 10; i++)
{
Subscriber subscriber = new Subscriber(publisher);
subscriber = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Subscriber.Count.ToString());
}
如果我运行它,我将输出0。 如果我从代码中删除事件订阅,我将得到期望的结果 - 这是10。
当调用 GC.Collect()时,gc被强制启动垃圾回收。由于订阅者在其中定义了 Finalize ,因此GC将暂停收集,直到 finalizequeue 为空 - 即所有订阅实例将调用其 Finalize()之后方法(如果我的假设是错误的,请纠正我)。在下一行 GC.WaitForPendingFinalizers()被调用,它将有效地暂停执行,直到终结器队列为空。现在,因为输出为0,我认为没有调用 Finalize(),这让我相信GC没有标记要收集的订户实例,因此 Finalizer()方法没有被调用。
所以我有两个问题
我唯一的猜测是,由于有10个订阅者实例正在引用同一个发布者实例,因此当GC收集发生时,它会看到有其他对发布者的引用,因此无法收集它,并且作为结果所有订阅实例与发布者一起被移动到下一代,因此在代码执行到达 Console.WriteLine时,不会发生垃圾收集,也不会调用 Finalize() Subscriber.Count.ToString())
我是对的还是我错过了什么?
答案 0 :(得分:4)
你错误地识别出真正发生的事情,这是C#中一个非常常见的陷阱。您需要运行测试程序的Release版本并在没有调试器的情况下运行它(按Ctrl + F5)。它将在用户的计算机上运行的方式。现在注意到,无论你是否订阅了这个活动,完全无关紧要,你将永远得到10个。
问题是,当您使用调试器时,不会收集发布者对象。我在this answer中详细解释了原因。
稍微扩展一下,你在这里有循环引用。订阅服务器对象引用Publisher对象。 Publisher对象具有对Subscriber对象的引用。循环引用不足以使对象保持活动状态。谢天谢地,如果是这样的话,垃圾收集不会很有效。发布者对象必须在别处引用以保持活跃,本地变量不够好。
答案 1 :(得分:3)
这两个问题的答案都是肯定的。