我们什么时候应该为组件实现带有 IContainer 参数的构造函数?

When should we implement a constructor with the IContainer parameter for a Component?

假设我有一个 WinForms 组件。
它可以是基于System.ComponentModel.Component or System.Windows.Forms.Controlclass的class(实际上Control继承了Component)。

我的组件可能会使用我们应该正确处理的其他 .NET classes - 钢笔、画笔、ContextMenuStrip 等。我根据众所周知的 IDisposable 模式在 Dispose(bool disposing) 方法的实现中按预期处理它们。

我想知道,我什么时候必须添加一个接受 IConatiner 参数的特殊构造函数,以便表单设计器在表单关闭期间注册我的组件的实例以自动处理资源:

private void InitializeComponent()
{
    this.components = new System.ComponentModel.Container();
    ...
    this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
}

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

我分析了一些WinForms组件的Dispose(bool disposing)方法的源代码试图找到答案,但情况仍然不清楚。
例如,ContextMenuStrip 组件有这样的构造函数,但 DataGridView 没有——尽管它们在 Dispose(bool disposing) 实现中都有足够的对象。

有什么提示或想法吗?

如果您是组件作者,作为最佳实践,我的建议是实现接受 IContainer 的构造函数重载,因为当组件具有这样的构造函数时,当开发人员在设计中删除组件的实例时表面上,设计者生成了一段代码,它关心组件的处理。

  • 您的组件在 ToolBox 中可用吗?

    对于可通过工具箱访问的组件,如果用户将组件放在设计器上,那么他们不应该担心组件的处置。所有标准组件都遵循这种模式,用户从不关心如何处理他们通过设计器创建的控件和组件,因此组件作者有责任关心设计时支持。

  • 你的组件里有什么要处理的吗?

    如果你没有任何东西要处理,那么这个重载就不是那么重要,但如果你有任何东西要处理,那么你应该关心处理。如上一个要点所述,由于用户可能会将您的组件放在设计器上并指望设计器处理它们,因此您有责任提供该方法以便设计器生成处理代码。否则,组件将不会被释放,从而导致 memory/handle 泄漏。

作为组件用户

作为组件用户,如果您在代码中创建组件(而不是在设计时删除它),您有责任在不再需要时处理该组件。您可能会发现此 post 有用:

如果您从工具箱中删除了一个组件,那么您无需担心它的处置问题。所有 标准组件 生成一个代码 o 在处理表单时处理组件。

设计者生成的代码如何处理组件的处置?

对于具有接受 IContainer 的构造函数的组件,当您将它们放在设计表面时,设计器会生成一个代码来创建 components 集合并将组件添加到该集合中,然后在处理表单时处理集合(包括其所有组件)。

查看以下设计器生成的代码:

private System.ComponentModel.IContainer components = null;

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

InitializeComponent()
{
    this.components = new System.ComponentModel.Container();
    ...
    ...
    this.myComponent = new MyComponent(this.components);
    ...
    ...
}

Dispose method of Container class:

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;
        }
    }
}

您会看到当您的表单被释放时,它会释放它在容器中找到的所有组件。

不需要使用 IContainer 创建额外的构造函数。如果你省略了这个,那么你必须自己将Control添加到IContainer。

public void MyForm()
{
    InitializeComponents();

    this.components.Add(this.MyUserControl);
}

如果不将此添加到 this.components,则必须显式 Dispose MyUserControl。 额外的问题:如果没有人使用 this.components,那么 this.components 仍然是 null。

所以总而言之,每当你创建一个必须被释放的控件时,添加额外的构造函数的工作量就会减少。毕竟是单行本

public MyUserControl()
{
    InitializeComponents();

    ...
}

public MyUserControl(IContainer container) : this()
{
    container?.Add(this);
}

令人欣慰的是,visual studio 设计者识别了这个构造函数并将使用它。它甚至会为您创建容器。