Extensibility

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Two.  Comparing COM and .NET


No matter how feature-rich your software components are, your users will always want more. A software bus must support some means for developers to extend the functionality of their software components . When I talk about extensibility here, I am really talking about clients (usually other developers) who buy a software component and then want to extend the functionality of those components in some way. If developers who created a software component want to extend the functionality of a component, they face a versioning, not an extensibility, problem. The developers of a software component have the source code for the component, so enhancing the component is easy. Their biggest concern is not breaking existing software that uses the component (versioning). Developers who buy the software component will likely not have the source code for the component. Therefore, they need extensibility at the binary level, or the ability to inherit from a software component for which they do not have the source code.

If you talk to software developers who know anything about object-oriented design and programming and you mention extensibility, the first word out of their mouths will likely be inheritance. One of the central ideas in object-oriented design and programming is that, given a type (a class), I should be able to create a subtype that inherits the state of its base class. This subtype should be able to either inherit the operations in its base class as-is, or override some of the operations in the base class and provide its own implementation. Unfortunately, COM did not support implementation inheritance. The COM software bus gives you two choices for extending a COM object: aggregation and containment. In both cases, you create an outer class that contains all of the interfaces that the extended COM class will support. In other words, the interfaces that the base class supports and any new interfaces that the extended object will add to the base class. The base class was called an inner class. The outer object will typically create an instance of the inner class on initialization and hold onto a reference to the inner object for as long as it lives. The main difference between containment and aggregation is the manner in which the outer object uses the inner object.

If you used containment to extend a COM class, the outer class would provide its own implementation of the interfaces in the base/inner class. This implementation would delegate to the implementation in the inner object. The key point is that the inner object is never directly exposed to a user of the outer object. Figure 2-10 shows schematically what containment looks like.

Figure 2-10. Extending a COM software component using containment.

graphics/02fig10.gif

If you used aggregation to extend a COM class, you directly expose interfaces from the inner object. When a user of the outer object requests an interface that is implemented in the inner object, the outer object would simply fetch and return an interface pointer on the inner object. Figure 2-11 shows a schematic view of aggregation.

Figure 2-11. Extend a COM software component using aggregation.

graphics/02fig11.gif

Although aggregation seems simpler and certainly more elegant than containment, conceptually, it is actually quite complicated. For a start, one of the basic rules of COM is that all of the interfaces that are supported by a COM class should be available from any interface. In other words, if a COM class supports interfaces A, B, and C, you should be able to obtain interfaces A and B given a pointer to interface C. Similarly, given a pointer to interface B, you should be able to obtain pointers to interfaces A and C. Unfortunately, if A and B are the interfaces supported by the inner object and C is the interface supported by the outer object, there is no way for a pointer to interface A to return a pointer to interface C because the implementation of the inner object knows nothing about interface C. The solution is for the inner object to delegate its implementation of the IUnknown interface to the outer object. The inner object of course has to be implemented in such a way that it knows when it is part of an aggregate. Confused? A lot of people were, and I always encouraged my COM students not to get discouraged if they did not understand aggregation right away. It was very complex, especially when compared to the elegant simplicity of implementation inheritance.

Publicly, Microsoft stated that the reason that COM did not support implementation inheritance was because of the "fragile base-class problem." This basically means that, when you use implementation inheritance, it is possible for a subclass to break its base class by overriding its methods with an unsuitable or incomplete implementation, perhaps failing to call the base class method to perform some necessary work. They also stated that polymorphism was the real goal and that implementation inheritance was only one way to achieve that goal.

They obviously thought otherwise privately because the .NET Framework supports binary-level, programming language-independent implementation inheritance. Given a managed type in an assembly, you can create a subtype using any programming language that supports .NET. Therefore, you can create a base class in C# and inherit from it using VB.NET. You are limited by the CLR to single inheritance, but still this is a huge step forward from COM. Has the "fragile base-class problem" suddenly gone away in .NET? In a wordNo. You can still get yourself into trouble in a hurry using implementation inheritance. Before you inherit from a class, make sure you understand the base class well enough that you understand the ramifications of overriding a method in the base class. In many cases, you will need to call the base class's implementation of the method in your override. If you do not have the source code for a base class, it can be difficult to know when it is safe to override a method. Fortunately, the CLR provides some help with this problem. First, the CLR allows for both virtual (overridable) and nonvirtual methods in a class. If overriding a particular method in one of your classes may cause problems, make sure you declare it as nonvirtual. With C#, methods in a class are nonvirtual by default; you have to use the virtual keyword explicitly to make a method overridable.

The CLR also supports the notion of sealed classes, that is, classes that cannot be subtyped. The following code shows how you would create a sealed class in C#:

 sealed class MyClass { //... } // This will cause a compiler error class NoCanDo : MyClass { //... } 

You should get in the habit of declaring a class as sealed if you know that inheriting from the class may cause problems.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net