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

Viewed 328

Let's assume I have a WinForms Component.
It can be a class based on the System.ComponentModel.Component or System.Windows.Forms.Control class (actually Control inherits Component).

My Component may use other .NET classes we should dispose correctly - Pens, Brushes, ContextMenuStrips and the like. I dispose them as expected in an implementation of the Dispose(bool disposing) method according to the well-known IDisposable pattern.

I wonder, when I must add a special constructor accepting an IConatiner parameter so the Form Designer will register an instance of my Component for automatic resource disposal during the Form shutdown:

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

I analyzed the source code of the Dispose(bool disposing) method of some WinForms Component trying to find an answer, but the situation is still not clear.
For example, the ContextMenuStrip Component has such a constructor, but DataGridView doesn't - though both has enough objects they dispose in their Dispose(bool disposing) implementations.

Any hints or ideas?

2 Answers

If you are a component author, as a best practice, my advice is implementing constructor overload which accepts IContainer, because when a component has such constructor, when developer drops an instance of the component on the design surface, the designer generates a piece of code which cares about dispose of the component.

  • Is your component available in ToolBox?

    For components which are accessible through toolbox, if the user drops the component on designer, then they should not be concerned about disposing of the component. All the standard components follow this pattern and users are never concerned about dispose of the controls and component which they create through designer, so it's component author's responsibility to care about design-time support.

  • Do you have anything to dispose in your component?

    If you don't have anything to dispose, then this overload is not that important, but if you have anything to dispose, then you should care about dispose. As mentioned in the previous bullet point, since users may drop your component on designer and count on designer to dispose them then it's your responsibility to provide that method so designer generated the dispose code. Otherwise, the component will not be disposed which results in memory/handle leak.

As a component user

As a component user, if you create the component in code (and not dropping it at design-time) you are responsible to take care of disposing the component when you no more need it. You may find this post useful: Why should I insert a non-UI Windows.Forms component from the designer?

If you drop a component from toolbox, then you don't need to worry about its disposal. All the standard components generate a code o dispose the component on dispose of the form.

How the designer generated code handles dispose of the components?

For the components which have a constructor which accepts IContainer, when you drop them on design surface, the designer generates a code to create a components collection and add the component to that collection, then disposes the collection (including all its component) when the form disposes.

Looking into the the following piece of designer-generated code:

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

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

You see when your form is disposed, it will dispose all the components that it finds in the container.

It is not required to create the extra constructor with the IContainer. If you omit this, then you have to add the Control to the IContainer yourself.

public void MyForm()
{
    InitializeComponents();

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

If you don't add this to this.components, you have to explicitly Dispose MyUserControl. Extra problem: if no one uses this.components, then this.components will still be null.

So all in all, it is way less work to add the extra constructor whenever you create a control that must be Disposed. After all, it is a one-liner.

public MyUserControl()
{
    InitializeComponents();

    ...
}

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

The nice thing is, that visual studio designer recognizes this constructor and will use it. It will even create the Container for you.

Related