
Introduction
A couple of days ago, I've got an email from a fellow developer with a rather interesting question concerning the Visual Studio .NET Windows Forms designer. The guy asked about the meaning of the components
member generated by the designer and why it is not used in most of the Form
s he designed.
The components
member is generated as part of the "Windows Forms Designer generated code" region, which is part of every Form
created and managed by means of the Visual Studio .NET Windows Forms designer:
#Region " Windows Form Designer generated code "
...
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Private components As System.ComponentModel.IContainer
...
#End Region
The code and the comments indicate that the components
member is used by the Form
to hold the list of components in order to be able to dispose the components as part of the Form.Dispose
call.
I've checked the designer-generated code in all my .NET Windows Forms projects and I've realized that the components
member is actually used only when a component having a specific constructor is placed onto a Form
.
You might recall that a component is a class that implements the System.ComponentModel.IComponent
interface (either directly or by deriving from a class that already implements the interface, such as System.ComponentModel.Component
). If the component exposes a constructor with the specific signature Public Sub New(ByVal c As IContainer)
, then the components
Form member is instantiated and passed to the component's constructor:
#Region " Windows Form Designer generated code "
...
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.components = New System.ComponentModel.Container
...
Me.ImageList1 = _
New System.Windows.Forms.ImageList(Me.components)
...
#End Region
Within the New(ByVal c As IContainer)
constructor, the component adds itself to the container by calling the IContainer.Add
method. Within the Form.Dispose
method, the components.Dispose
method is called ensuring that all the resources held by the Form
's components are correctly released.
At this point, it started to make sense, although I still wondered how are components without the New(IContainer)
constructor disposed off? I've found several such components in the System.Windows.Forms
namespace having only the default (parameterless) constructor, for example:
System.Windows.Forms.ColumnHeader
System.Windows.Forms.DataGridTableStyle
System.Windows.Forms.ColorDialog
System.Windows.Forms.FontDialog
System.Windows.Forms.OpenFileDialog
System.Windows.Forms.SaveFileDialog
...
Regarding their "dispose" behavior, the classes can be roughly divided into two groups:
The classes in the first group are always contained within a parent component (or control), so they are disposed along with their container. For instance, System.Windows.Forms.ColumnHeader
instances are contained within the System.Windows.Forms.ListView.Columns
collection. The System.Windows.Forms.DataGridTableStyle
instances are contained within the System.Windows.Forms.DataGrid.TableStyles
collection.
The classes in the second group (represented by the System.Windows.Forms.*Dialog
classes above) are NOT disposed as part of their owning Form
disposal. I can only guess that the classes either don't hold onto any unmanaged resources during their lifetime, or there are some other mechanisms for releasing their resources (Windows messages come to mind, for example).
In order to verify the above-mentioned disposable behavior, I've created a Windows Application project implementing two components (you can download the solution here). The SimpleComponent
class was created WITHOUT the New(IContainer)
constructor. The ContainedComponent
class was created WITH the New(IContainer)
constructor:
Public Class SimpleComponent
Inherits System.ComponentModel.Component
Public Sub New()
MyBase.New()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
Debug.WriteLine(String.Format("{0}({1})", _
Me.GetType().FullName, disposing))
MyBase.Dispose(disposing)
End Sub
End Class
Public Class ContainedComponent
Inherits System.ComponentModel.Component
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal Container As System.ComponentModel.IContainer)
Me.New()
Container.Add(Me)
End Sub
Protected Overloads Overrides Sub Dispose( _
ByVal disposing As Boolean)
Debug.WriteLine(String.Format("{0}({1})", _
Me.GetType().FullName, disposing))
MyBase.Dispose(disposing)
End Sub
End Class
After recompiling the solution, I was able to add the two components to the Visual Studio .NET toolbox and I've dropped both of the components onto a DialogForm
form class. Here is the instantiation code that the Windows Forms designer generated for the two components:
#Region " Windows Form Designer generated code "
...
Me.components = New System.ComponentModel.Container
Me.SimpleComponent1 = New DisposableComponents.SimpleComponent
Me.ContainedComponent2 = _
New DisposableComponents.ContainedComponent(Me.components)
...
#End Region
Because the ContainedComponent
class exposes the New(IContainer)
constructor, the designer generated code to call this constructor instead of the default one. In the constructor, the ContainedComponent
instance adds itself to the Form.components
container, so it is automatically disposed when the Form
's Dispose(Boolean)
method is called.
In contrast, the SimpleComponent
instance does not really know when the owning Form
is disposed, so it remains alive until it becomes garbage-collected eventually.
In the application's main form, the following code is used to display the DialogForm
containing the two components:
Private Sub ShowDialogButton_Click(...)
Dim Dialog As DialogForm
Try
Dialog = New DialogForm
Dialog.ShowDialog(Me)
Catch ex As Exception
Trace.WriteLine(ex.ToString())
MsgBox(ex.Message, MsgBoxStyle.Exclamation)
Finally
If Not Dialog Is Nothing Then
Dialog.Dispose()
End If
End Try
End Sub
When you run the application and click the ShowDialogButton
button, the DialogForm
instance is displayed. If you dismiss the dialog, the DialogForm.Dispose
method is called and you'll see in the Output window that the ContainedComponent.Dispose
method has been called as well.
In contrast, the SimpleComponent
instance is NOT disposed until after the application is ended or until garbage collection takes place. (It might happen if you create and destroy several instances of the DialogForm
so enough memory is allocated that a threshold for the garbage collection is reached. Just press and hold the ENTER key and you'll reach the threshold quickly.)
After looking at how the Windows Forms designer handles components, I think it might be useful to have a quick look at how Control
s are handled with regards to IDisposable
.
Every Windows Forms control derives from the System.Windows.Forms.Control
base class, which exposes the Controls
property:
Public ReadOnly Property Controls() As ControlCollection
The implementation of the Control.Dispose(Boolean)
is overridden in such a way that it iterates through the Controls
collection calling the Dispose
method for each member of the collection. The Windows Forms Designer "knows" this and the code it generates for Control
-derived classes always adds controls to the Control.Controls
collection that the Form
class inherits from the Control
class:
#Region " Windows Form Designer generated code "
...
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.TextBox1 = New System.Windows.Forms.TextBox
...
Me.Controls.Add(Me.TextBox1)
End Sub
...
#End Region
This way, when the Form
is being disposed, the contained controls are disposed automatically as well.
I don't really know why the Component
containment support is not built into the Form
class just like the Control
containment represented by the Form.Controls
collection. It would have been more consistent and also it would make the generated code smaller and more elegant. For example, it wouldn't have been necessary to neither generate the components
member declaration, nor the Dispose(Boolean)
method override. If someone is able to explain the reasoning behind this inconsistency, please let me know - I'm really curious about that.
This leads me to an important recommendation:
If you've been designing a component that allocates UNMANAGED resources, please double check that you:
- Implement the specific
Public New(IContainer)
constructor and add your component instance to the container by calling the IContainer.Add(Me)
method in the constructor. (If you create your component using the Visual Studio .NET 'Component Class' template, it generates the specific constructor for you properly.)
- Implement the
IDisposable
design pattern properly meaning that you do release the unmanaged resources and also that you do that exactly once.
Please, take a look here if you'd like to see an alternate IDisposable
design pattern - more robust and elegant than its .NET counterpart, IMHO.
History
February 1, 2004
Published.