System.Windows.Forms.Timer 和 C# 垃圾收集

System.Windows.Forms.Timer and Garbage Collection with C#

如果我创建一个与 Form.Designer.cs 文件无关的 System.Windows.Forms.Timer(例如,我只是在我的代码中的任何地方实例化计时器),并且我创建了一个 System.ComponentModel.Container 组件并创建一个要添加到它的控件,比如一个 NotifyIcon(这个 notifyIcon 不会有与之关联的图标)并将该 notifyIcon 添加到我的组件对象然后实例化 forms.timer 并将该组件作为计时器构造函数的参数,如果计时器的启用 属性 在程序的生命周期内设置为 false 并且计时器在程序的生命周期内从未被丢弃,计时器是否会被 GC 编辑?或者只要不直接处理该组件,定时器就不会受到垃圾回收的影响吗?

这是一个代码示例:

NotifyIcon notifyIcon = new NotifyIcon();
notifyIcon.Visible = false;
System.ComponentModel.IContainer components = new System.ComponentModel.Container();
components.add(notifyIcon);
System.Windows.Forms.Timer formTimer = new System.Windows.Forms.Timer(components);
formTimer.Enabled = true;
formTimer.Enabled = false;

感谢阅读

理论

如果对象 A 持有对对象 B 的引用,并且您将 null 分配给 A,那么一段时间后 A 将被垃圾回收。如果还有其他人引用 B,则 B 不会被垃圾回收。但是,如果 A 是最后一个引用 B 的,那么 B 也会被收集:

Person Joseph = new Person
{
    Id = 1, 
    Address = new Address {Id = 10, ...},
    ...
};

Joseph.Address = new Address {Id = 11, ...}

一段时间后,ID 为 10 的地址将被垃圾回收,因为没有人再持有对它的引用。

Person Mary = new Person
{
    Id = 2, 
    Address = Joseph.Address,
    ...
};
Joseph = null;

一段时间后,Id 为 1 的 Person 将被垃圾回收,因为没有人再持有对它的引用。但是,Id 为 11 的地址不会被垃圾回收,因为 ID 为 2 的人持有对它的引用。

mary = null;

现在没有人持有对 ID 为 11 的地址的引用,因此它将被垃圾收集。

回到你的问题

  • 您创建了一个新的 Container 对象
  • 您创建了一个新的 NotifyIcon 对象。
  • 您将此 NotifyIcon 对象添加到组件对象
  • 您创建一个新的 Timer 对象,使用命令计时器将其自身添加到 Container 的构造函数。

所以 Container 对象引用了创建的 NotifyIcon 和 Timer 对象。只要这些对象没有从 Container 中移除,并且容器没有被 Disposed 也没有被垃圾回收,这个 Container 就会持有这些引用,因此 notifyIcon 和 Timer 都不会被垃圾回收。

components.Dispose();

根据 reference source of System.ComponentModels.Container 这将执行以下操作:

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        lock (syncObj) {
        while (siteCount > 0)
        {
            ISite site = sites[--siteCount];
            site.Component.Site = null;
            site.Component.Dispose();
        }
        sites = null;
        components = null;
    }
}

所以Container.Dispose()会调用Dispose()所有添加的组件,然后释放对添加组件的引用

  • 如果您在其他地方引用了这些组件,那么这些组件将不会被垃圾回收。但是,由于它们是 Disposed,通常它们是不可用的。
  • 如果 Container 是唯一具有引用的容器,则在 Container.Dispose() 之后,NotifyIcon 和 Timer 有资格被收集。
  • 因为您仍然持有对 Container 的引用,所以不会收集 Container 本身。既然是Disposed,Container就不能再用了。
  • 为确保容器被收集,要么让引用超出范围,要么为它分配 null。

在每个 Form.Dispose() 中,您会发现类似于以下的代码:

private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
        components.Dispose();
    }
    base.Dispose(disposing);
}

当表单被处置时,this.components 也被处置,这意味着它持有的所有 IComponents 都被处置,并且引用被删除。甚至对 Container.ComponentCollection 的引用也被删除了。请注意,该表单仍然包含对 this.components 的引用,因此即使不能再使用 this.components。

后者有点奇怪:如果您不再保留对 NotifyIcon / Timer 的引用,它们将被垃圾收集,但是 this.Components 不会,只要表单存在。如果 Form.Dispose 也发布了对 this.components

的引用,那就更好了