Introduction to Components

Summary

Quick Review of Garbage Collection

Finalize

For managed objects you can rely on the garbage collector to clean-up after you. For unmanaged resources however, you must explicitly release them when done with them. The most common type of an unmanaged resource is one that wraps an OS resource such as file, network connection, or window handle. For these types of objects, Object.Finalize method should be used to free resources. However, in C# and Managed C++ you cannot explicitly call Finalize - you have to use destructor syntax which generates a call to Finalize and a call to the base class's Finalize method (In C# and Managed C++, Finalize cannot be called or overloaded.)

The garbage collector keeps track of objects that have Finalize methods using what is called a finalization queue. Each object that has a Finalize method (destructor in C# and Managed C++) is placed in the finalization queue when that object is created. This way when the garbage collector attempts to free an object's memory, it first checks the finalization queue and if that object was found in there, its Finalize method is called and hence external resources are freed before destroying the object. The Finalize method (destructor in C# and Managed C++) should only be used to safeguard against resources not being freed explicitly. This takes us directly into IDisposable.Dispose.

Summary: Implement a destructor that calls the object's IDisposable.Dispose method. This topic is fully discussed in the Finalize method section under Data Structures.

Components and Controls

Definitions

Component
A component in .NET Framework is a class that implements System.ComponentModel.IComponent interface or derives directly/indirectly from a class that implements this interface. Components you develop will most likely inherit from System.ComponentModel.Component which implements System.ComponentModel.IComponent.

Control
A control in .NET Framework is a class that provides user-interface capabilities and derives either from System.Windows.Forms.Control or  System.Web.UI.Control.

Note that a component does not usually have a GUI, whereas controls usually do.

Component

In general, a component is generally used for an object that is reusable and can interact with other objects. A .NET Framework component satisfies these features and provides additional features such as control over external resources and design-time support.

Again, components you develop will most likely inherit from System.ComponentModel.Component. This is the base class for all components in the .NET Framework runtime. This class is remotable, derives from MarshalByRefObject and implements IComponent.

Control over external resources

An IComponent-derived interface allows control over external resources because IComponent derives from IDisposable. As we know, IDisposable.Dispose method is used to release resources explicitly. IDisposable.Dispose provides a deterministic way of freeing up resources as opposed to the non-deterministic garbage collector. Note the following points emphasized in Quick Review of Garbage Collection for controlling external resources in an IComponent-derived component:

Design-Time Support

A class that is a component (derived from System.ComponentModel.IComponent) is designable in VisualStudio.NET in the sense that it can be added to the toolbox and dragged and dropped on a form and manipulated on a design surface. Only classes that directly or indirectly implement System.ComponentModel.IComponent can be added to a toolbox of a visual designer and can be dragged and dropped to a design surface.

Hosting a Component

Container: A container is any class that implements the System.ComponentModel.IContainer interface directly/indirectly. A container typically contains one or more components that are called the container's child components. In VisualStudio.NET the designer on which a control is placed is actually a container.

Site: A site is any class that implements the System.ComponentModel.ISite interface directly/indirectly. Sites are provided by a container to manage and communicate with its child components. A container and a site are typically implemented as a unit.

A component can be sited (hosted) in a container using the container's site. When a component is sited, it interacts with its container through the site. To properly manage resource cleanup between the container and its child components, the container must implement IDisposable.Dispose as follows: release all resources held by the container and invoke the Dispose method of each of its child components.

Important Note: Containment is logical and need not be visual. In the visual case, a main application may act as a container that contains multiple child forms defined as components. The container application interacts with its child forms using sites. In the logical case, a middle-tier component may act as a container for a database access component again using sites.

Marshaling a Component

Components can be remotable or non-remotable. Remotable objects can be marshaled either by reference or by value. When you marshal a remotable component by reference to the client, a proxy is created on the client and it is this proxy that the client deals with as if it were the real remote component (in reality the proxy forwards the calls to the actual remote object). When you marshal a  remotable component by value to the client, an actual copy of the component is serialized and created on the client-side. The client then deals with this local copy only.

Marshaling a remotable component by reference

Remotable components that encapsulate system resources, that are large, or that simply must be a CAO or an SAO in .NET Remoting scenarios (see .NET Remoting), must be marshaled by references. The base class should be System.ComponentModel.Component. This base class implements IComponent and derives from MarshalByRefObject.

Marshaling a remotable component by value

Remotable component that simple hold state or that do not fit as a  CAO or an SAO in .NET Remoting scenarios (see .NET Remoting), must be marshaled by value. The base class should be System.ComponentModel.MarshalByValueComponent. This base class implements IComponent and derived from System.Object.

Only a few classes in .NET Framework derive from MarshalByValueComponent. All these components are in the System.Data namespace such as DataTable, DataSet, DataColumn, DataView and DataViewManager.

Non-remotable components

If a component will not be remoted, it must implement (directly or indirectly) IComponent

Control

Recall that a control in .NET Framework is a class that provides user-interface capabilities. The .NET Framework provides two base classes for controls:

Properties

In general, components should use properties rather than public fields. For example:

Properties are defined in the usual way. For example:

public Color BackColor
{
    get { return m_BackColor; }
    set { m_BackColor = value; }
}

The type of a property can be a primitive type, a collection of primitive types, a user-defined type, or a collection of user-defined types.

Events

Raising an event from a component is no different than raising an event from a class that is not a component. Event functionality is provided by three inter-related elements:

  1. A class that provides event data.
  2. An event delegate.
  3. A class that raises the event.

Bases on conventions set forth by .NET, if you want your class to raise an event called EventName, you will need the following elements:

  1. The class that provides event data  should be called EventNameEventArgs. This class must derive from System.EventArgs.
  2. The delegate must be called EventNameEventHandler.
  3. The class that raises the event should provide:

The following example illustrates:

/// <summary>
/// Step 1: Define a class to procide data for the event
/// </summary>
public class PowerOnEventArgs : System.EventArgs
{
    /* Data members */
    DateTime dtTime;
    int      nBatteryPowerAvailable;
    bool     bNetworkPresent;

    /* Constructors */
   
public PowerOnEventArgs() {}

    /* Properties */
    public DateTime Time
    {
        get { return dtTime; }
        set { dtTime = value; }
    }

    public int BatteryPower
    {
        get { return nBatteryPowerAvailable; }
        set { nBatteryPowerAvailable = value; }
    }

    public bool NetworkPresent
    {
        get { return bNetworkPresent; }
        set { bNetworkPresent = value; }
    }
}

/// <summary>
/// Step 2: Define a delegate for the PowerOn event. Functoins whose signautre matches
/// the following delegate function will be able to trap the PowerOn event
/// </summary>
public delegate void PowerOnEventHandler( object sender, PowerOnEventArgs args );

/// <summary>
/// Step 3: Declares a class that raises the PowerOn event
/// </summary>
public class Laptop
{
    // Data members
    ...


    // Events
    public event PowerOnEventHandler PowerOn;

    // Public interface
    public void SwitchOn()
    {
        // Turn on the laptop

        // Perform various system initializations

        // When all is done, fire the PowerOn events
        PowerOnEventArgs e = new PowerOnEventArgs();
        e.BatteryPower = 100;
        e.NetworkPresent = true;
        e.Time = DateTime.Now;
        OnPowerOn( e );
    }

    // Protected methods
    protected virtual void OnPowerOn( PowerOnEventArgs e )
    {
        if (e != null)
            PowerOn( this, e );
    }
}

// Creates an object that fires an event
private void btnEvent_Click(object sender, System.EventArgs e)
{
    // Create a Laptop class and handle its events
    Laptop obLaptop = new Laptop();
    obLaptop.PowerOn += new PowerOnEventHandler( Laption_PowerOn );
    obLaptop.SwitchOn();                 // Fires an event
}

// Handles the PowerOn event
private void Laption_PowerOn( object oSender, PowerOnEventArgs e )
{
    // Collect info form the PowerOn event
    bool bNetworkPresent    = e.NetworkPresent;
    int nBatteryChargeLevel = e.BatteryPower;
    ...
}

Design-Time Attributes for Components

Note: Refer to "Enhancing Design-Time Support" in MSDN for full details. The following only introduces the basic concepts.

Components that can be displayed in designers such as VisualStudio.NET need attributes that provide meta-data to design tools. These attributes are often called design-time attributes and are essential for displaying the control and its members correctly at design-time. For example, in the following code, Category is a design-time attribute that causes the affected property, TextAlignment, to be displayed in the given category, Alignment:

// This attributes is applied to a property within a class
[Catgory("Alignment")]
public ContentAlignment TextAlign
{
    get { return m_contentAlginment; }
    set { m_contentAlginment = value; }
}

On the other hand, some attributes may be applied at the class level. For example, the Designer attribute is applied at the class level to inform the forms designer which designer class should be used to display the control:

// To get more information about design-time attributes that associate designers with components, see Enhancing Design-Time Support in MSDN
[Designer(typeof(MyControls.Desinger))]
public class MyMaskedTextBox : TextBox 
{ ... }

The following also shows attributes applied at the class and event level:

[DefaultEvent(ValueChanged)]
[DefaultProperty(Number)]
public class MyNumberBox : TextBox
{
    [DefaultValue(false)]
    public bool ReadOnly
    { ... }

    [DefaultValue("0")]
    public int Number
    { ... }

    [Descirption("Raised when value is changed")]
    public event ValueChangedEventHandler ValueChanged;
    
}

Some of the more common attributes are:

Design-Time Attributes and Inheritance

When you derive a component or a control from a base component or control that has design-time attributes, the derived component will inherit the design-time functionality of the base class. Note that you can always override attributes or apply additional attributes to the derived component.

In the following example, the derived class overrides the Browsable attribue for the Text property which was inherited from the base class with Browsable set to false:

public class MyBase
{
    [Browsable(true)]
    public virtual string Text { ... }
}

public class MyDerived : MyBase
{
    [Browsable(false)]
    public override string Text { ... }
}

Design-Time Attributes and Converters, Editors & Designers

Converters and Editors in general allow you to better manage properties of a component by providing design-support classes where as designers allow to enhance or restrict the visual design of your component. The following  diagram illustrates where converters, editors, and designers fir in the design-time architecture of components. 

Type Converters

Specifically, a converter (also known as type converter) is a class that converts one data type to another. In a host such as a property browser in a forms designer, a type converter allows a property value to be displayed as text to the developer. A type converter also allows a text value entered by a developer for a given property to be converted into the appropriate data type. A typical use of type converters in found in the Size property of any component - it allows you to enter the size as a string for the X and Y components of Size. Type converters are implemented by deriving a class from System.ComponentModel.TypeConverter. For example:

public class MyClass : Component
{
    ...
    [TypeConverter(typeof(MyPointConverter))]
    public Point MyLocation
    {
        get { return m_ptLocation; }
        set { m_ptLocation = value; }
    }
    
}
public class MyPointConverter : System.ComponentModel.TypeConverter
{
    ...
}

UI Type Editors

In some cases, type converters are inadequate in displaying simple text as the value of a property in a property browser. For example, in the case of a Color property, visual representation is much better than simple text representation. In this case, a UI Type Editor allows visual representation of Color values. Therefore, a UI Type editor is a class that can provide a GUI element for representing and editing values of properties. Type editors are implemented by deriving a class from System.Drawing.Design.UITypeEditor.

public class MyClass : Component
{
    ...
   
    [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]
    public Color MyColor
    {
        get { return m_clColor; }
        set { m_clColor = value; }
    }
}
public class MyColorEditor : System.Drawing.Design.UITypeEditor
{
    ...
}

Designers

A designer is a component that can modify the design-time behavior of a component. For example, let's say you want to have a standard size for all buttons and you want to prevent developers from  resizing the button (using resizing handle) once placed on the designer. This effect can easily be achieved using a designer class. To see how this specific feature can be done, see SelectionRules in MSDN.

Designer can be implemented either by deriving and implementing System.ComponentModel.IDesigner interface, or by overriding methods from ComponentDesigner class for components (or  ControlDesigner for controls)

[Designer(typeof(MyDesigner))]
public class MyClass : Component
{
    ...
}

public class MyDesigner : ComponentDesigner
{
    ...
}

Class vs. Structure

Recall that a component must either implement System.ComponentModel.IComponent, or it must have System.ComponentModel.Component class somewhere in its base class hierarchy. Having said that, you can then implement your component using either a class or a struct. The following table summarizes the differences between implementing a component using a class or a struct:

Category Class Structure
Default access Level public public
Default access level of data members private public
Default access level for properties and methods private public
Value or Reference type? Reference Value
Can be a base for new types? Yes No
Can implement interfaces? Yes Yes.
Can raise and handle events? Yes Yes
Scope of members Class Structure
Constructors Parameterized and parameterless constructors allowed. Parameterized and parameterless constructors allowed.
Destructors? Yes No
Can contain nested types? Yes Yes
Can be a nested type? Yes Yes

Structures

One of the more important aspects to keep in mind about structures when deciding to use a structure rather than using a class is that a structure is a value type.  In other words, the structure' type contains the actual data of the structure rather than containing a reference (pointer) to data. 

Note: Although a structure can have properties, methods, and events, these do not affect the space occupied by a structure type. The space allocated for a structure is as large as the structure's instance data.

Another equally important fact about structures is that cannot be extended. In other words, you cannot derive from a structure type.

For a full review of structures see Struct Types.

Classes

Classes on the other hand are can do everything that structures cannot do. With respect to component development, designing a component as a class allows your component to act as base components for other more specialized components (if you do not want your component to be used as a base class, you can always use the sealed modifier). Or you can author a component whose sole purpose is to be used as a base component for other components by applying the abstract modifier to the  the component class declaration.

From a component-development perspective, using a class to declare a component allows you to declare a finalizer (destructor) that can be used to free resources if the user forgot to free them using Dispose.

Class or Structure?

From the above table and the discussion that followed:

Authoring Components

How do you start developing a component. There are many ways, but the following offers a balanced approach:

Example

You can easily create a skeletal component (with cleanup code as discussed in Quick Review of Garbage Collection) by selecting Component Class from Add New Item dialog box:

The following is a very basic skeletal component:

public class MyComponent : System.ComponentModel.Component
{
    /* Data members */
    private Color m_clBackColor = Color.Aqua;

    // Required designer variable
    private System.ComponentModel.Container components = null;

    /* Constructors */
    public MyComponent(System.ComponentModel.IContainer container)
    {
        /// Required for Windows.Forms Class Composition Designer support
        container.Add(this);
        InitializeComponent();
    }

    public MyComponent()
    {
        // Required for Windows.Forms Class Composition Designer support
        InitializeComponent();
    }

    // Clean up any resources being used
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if(components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }


    #region Component Designer generated code
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    private void InitializeComponent()
    {
        components = new System.ComponentModel.Container();
    }
    #endregion

    /* Public properties */
    public Color MyColor
    {
        get { return m_clBackColor;}
        set { m_clBackColor = value; }
    }
}

Summary of Relevant Interfaces

The following table lists the relevant interfaces and classes when used with component development:

Entity Interface / Class
Component System.ComponentModel.IComponent
System.ComponentModle.Component
Control System.Windows.Forms.Control
System.Web.UI.Control
Container System.ComponentModel.IContainer
Site System.ComponentModel.ISite
Remotable component marshaled by reference System.ComponentModel.Component
Remotable component marshaled by value System.ComponentModel.MarshalByValueComponent