Inheritance, Aggregation, and Containment

COM reusability in the .NET Framework is accomplished through inheritance. COM types can participate in inheritance as a base class. Use the inheritance, aggregation, or containment models under the following circumstances:

Model Use for
Inheritance Exposing the managed object as the outer object.
Aggregation Enabling the outer object to expose another object's implementation of an interface without modification.
Containment Enabling the outer object to modify the behavior of the inner object.

Inheritance

When managed interfaces are exposed to COM, they always extend IUnknown or IDispatch, even when the interface is inherited from another interface on the managed side. The same rule applies to the class interfaces that are generated for managed classes.

The .NET Framework extends the COM model for reusability by adding implementation inheritance. Managed types can derive directly or indirectly from a COM coclass; more specifically, they can derive from the runtime callable wrapper generated by the runtime. The derived type can expose all the method and properties of the COM object as well as methods and properties implemented in managed code. The resulting object is partly implemented in managed code and partly implemented in unmanaged code.

To qualify as a base class, the coclass must:

Managed types can extend the RCW for a qualifying coclass and override the methods provided by the base object. You must override all base methods of an interface if you want to override any of the methods.

A managed type inherits from an RCW the same way that it inherits from a managed base object. In the following code example, the managed Catapult class derives from AcmeLib.Slingshot, a COM type.

#using "AcmeLib.dll"    // Provides definition of Slingshot.

__gc class Catapult : public AcmeLib.Slingshot  // Extends the COM type.
{
    // Delegates to base implementation.
    Load() { //... };  
   
    Fire()               
    {
        // Engages in some behavior before delegating to the base 
        // implementation.
        Slingshot::Fire();
    }

    // The Aim method needs to be overridden.
    Aim() { //... }         
}
Catapult *cp = new Catapult();

// Calls derived implementation.
cp->Load();
// Calls base implementation.
cp->Aim();
// Calls derived which delegates to base.
cp->Fire();

Aggregation

To expose the interfaces of one COM class as though they were implemented on a second COM class, the second class aggregates the first. A COM object can aggregate a .NET object, in which case all of the object's interfaces, including its class interface, are available through the outer object. The inner .NET object delegates calls to its IUnknown methods to the controlling IUnknown.

Aggregation is slightly more complex than containment (described in the next section). You typically use it to enable the outer object to expose another object's implementation of an interface without modification. All managed objects automatically support COM-style aggregation with the managed object being used as the inner object. To aggregate a managed object, the unmanaged outer object creates the managed inner object by calling CoCreateInstance, then passing the outer object's IUnknown as an OuterUnknown parameter. When an outer IUnknown is passed to a managed object during construction, the managed object caches the interface and uses it as follows:

  • The outer object holds onto the inner IUnknown's nondelegating IUnknown. The nondelegating IUnknown behaves as a normal IUnknown behaves; that is, it succeeds if the object implements the interface and fails otherwise. The nondelegating IUnknown does not forward the call to the outer object.
  • If the inner object is queried for an interface that it does not support, the inner object delegates the call to the outer object's IUnknown interface.
  • All calls to the QueryInterface, AddRef and Release methods of the inner object are delegated to the outer object's IUnknown.

These three behaviors make it possible to aggregate any managed object. With this type of aggregation relationship, it is possible to have a single COM object that is partly implemented in managed code (the inner portion) and partly in unmanaged code (the outer portion).

Containment

A .NET object can contain a COM object by importing its metadata into a .NET assembly, then declaring a data member of that type within another class. As with normal COM containment, you can call the COM object's interfaces in your own interface implementations, but the contained object is not exposed outside the class. Containment is simpler than aggregation. You typically use containment when the outer object needs to modify the behavior of the inner object. To accomplish this, the outer object simply creates an instance of the inner object in its constructor, and delegates calls to the inner object as necessary. The outer object can choose which calls to delegate and which calls to handle directly. The runtime has no special requirements for objects to support containment.

A COM object can also contain a .NET object. Behavior with respect to clients of the COM object is exactly the same as if the contained object was any other COM object.

See Also

Advanced COM Interop | Exposing COM Components to the .NET Framework | Exposing .NET Framework Components to COM