Last month, we discussed the power of .NET's multilanguage combination, but noted that C++ achieves it only through a major language update: Managed C++. Fortunately, this isn't the only possible approach. Languages can retain their own properties while benefiting from the common object model and its promise of full interoperability with other .NET languages.
Mapping Languages Into the Object Model |
Multiple Inheritance From Classes Unlike the .NET object model, Eiffel supports full multiple inheritance: A class D may inherit from classes A, B and C. In .NET, at most one of these three parents may be a class; the others must be interfaces, consisting only of methods devoid of any implementation. In Eiffel, there is no separate notion of interface; a class may be completely implemented, completely deferred (the equivalent of a .NET interface), or partially deferred, with some features implemented and others not. This full spectrum of class concreteness is a key feature of Eiffel, enabling you to start system analysis and design with very abstract classes, close to the problem domain, then bring them progressively and seamlessly to a more concrete state with fewer and fewer deferred features. |
Each .NET language must be mapped into the object model (see "Mapping Languages Into the Object Model"). The tricky part is deciding who takes care of the mapping. The managed C++ approach put the onus on the language designer (to change the language) and the application programmer (to adapt the program). It's possible, however, to let the compiler writers do the job. This is good news for everyoneat least, everyone who isn't in charge of porting a compiler to .NETsince it means you get the best of all worlds: You can continue to use your language as before, yet rely on components from other languages.
The example of multiple inheritance in the Eiffel language (see "Multiple Inheritance From Classes") illustrates how language implementers can save the day.
Unlike the .NET object model, Eiffel supports full multiple inheritance: Class
D
may inherit from classes A
, B
and C
.
In .NET, at most one of these three parents may be a class; the others, as we
know, must be interfaces, consisting only of methods devoid of any implementation.
In Eiffel, there is no separate notion of interface; a class may be completely
implemented, completely deferred (the equivalent of a .NET interface),
or partially deferred, with some features implemented and others not. This full
spectrum of class concreteness is a key feature of Eiffel, enabling you to start
system analysis and design with very abstract classes, close to the problem
domain, then bring them progressively and seamlessly to a more concrete state
with fewer and fewer deferred features, until nothing is deferred.
So how can you make D
inherit from fully or partially implemented classes A
,
B
and C
?
At first glance, you may believe that it won't work: The inheritance structure violates the rules of the .NET object model. The only solution, then, is to follow the C++ route and change the programming language, creating a single-inheritance-only variant. (Such a subset, called Eiffel#, was designed for Eiffel as a first iteration of the .NET version. Eiffel# was only a stepping stone and is now obsolete, as Eiffel for .NET now supports the full Eiffel language with multiple inheritance through the techniques explained in the following paragraphs.)
Taken from another angle, however, the problem is trivial. As long as it enables
Eiffel code to use multiple inheritance, the compiler can hide any signs of
inheritance from the rest of the world. Assuming, for example, that the classes
A
, B
, C
and D
have features
f
, g
, h
and i
, respectively,
it suffices to show to the .NET world a class D
that includes both
its own immediate feature, k
, and those inherited from its parents
(f
, g
, h
), and has no inheritance relationship
to A
, B
or C
. This is known in the Eiffel
environment as the flat form of a class: an inheritance-free equivalent
with all the features, local and inherited.
With this technique, the inheritance structure will still apply in the Eiffel
world, with its associated techniques of polymorphism and dynamic binding; for
example, with a1
of type A
and d1
of
type D
, you may have an assignment of the form a1 := d1
,
but to the rest of the world, D
is an orphan class.
The Real Challenge
This solution is not really acceptable, however. If you expose classes from
an OO language to other OO languages, you'll want to expose their inheritance
structure, also. With the solution shown, C# or VB.NET code wouldn't be able
to create an object of type D and, polymorphically, assign it to a variable
of type A, even though this is legitimate in view of the inheritance structure,
and permitted in Eiffel classes.
We won't stop at this first solution, but it serves to illustrate a key observation regarding the possibility of providing on .NET a language feature that's not supported by the .NET object model, such as multiple inheritance or genericity. A gut reaction like ".NET can't support a multiple-inheritance language" is just silly. Supporting a particular language feature is a question for compiler writers. If you can compile multiple inheritance into C or into Intel machine code, there's no reason you couldn't do it for the .NET virtual machine and its Intermediate Language (IL).
But that's only part of the question: In this multilanguage environment, can
we compile the construct and show other languages a reasonably accurate
view of the original structure? We may not be happy with a compilation technique,
in the multiple-inheritance example, that handles multiple inheritance but shows
A
, B
, C
and D
to the rest
of the world as if they were totally unrelated classes.
This is the true challenge of compiling a language on .NET that has multiple inheritance or some other advanced feature not directly supported by the object model: How much of the original set of properties can we retain in the view that we present to components written in other languages?
Relying on Interfaces
Let's see if we can show our partner languages a better view of our inheritance
graph than just a flat structure with no inheritance links. One way to give
them more clues would be to designate as "favorite," for each class,
at most one parent class. Based on some criteria, D
would admit to our foreign-language
friends that, for example, it inherits from A
, hiding the other
classes. The advertised inheritance structure would then conform to the .NET
rules.
This solution, however, is unattractive. Under what criterion could we possibly choose which parent to retain? Multiple inheritance is precisely intended to let a class combine several equally important abstractions.
Shadowing Classes by Interfaces For each Eiffel class, the Eiffel for .NET compiler produces both an interface and an implementation class. The implementation class inherits only from its associated interface. The original inheritance structure between Eiffel classes is preserved among the .NET interfaces: You can see that D inherits from A ,
B and C ,
just as in the original Eiffel class hierarchy. |
Eiffel for .NET offers a better solution: It takes advantage of the limited
form of multiple inheritance permitted by .NETmultiple inheritance from interfaces
(see "Shadowing Classes by Interfaces"). For each Eiffel class, the
Eiffel for .NET compiler produces an interface named after the original class,
as well as a related implementation class named by prefixing the original class
name with "IMPL
" and a period. Thus, if we originally
have Eiffel class D
inheriting from classes A
, B
and C
, the complier generates .NET interfaces A
, B
,
C
and .NET classes IMPL.A
, IMPL.B
and
IMPL.C
. Each implementation class will inherit from the associated
interface and from nothing else. The original inheritance structure between
Eiffel classes, single or multiple, is preserved among the .NET interfaces:
You can see D
inherit from A
, B
and C
,
as the corresponding classes did in the original Eiffel.
All this is set up to allow writers of non-Eiffel modules to use the Eiffel
classes through the resulting .NET interfaces, such as A
, and not have to know
about the implementation classes such as IMPL.A
or anything else
in the above scheme. A C# or VB .NET programmer may declare variables of the
corresponding types, as an Eiffel programmer would in:
A your_A_variable; /* Declare variables with the appropriate types */<br> D your_D_variable;
You may then use polymorphism to assign a value of type D
to a
target whose type is any one of A
, B
or C
;
for example:
your_A_variable = your_D_variable
If a non-Eiffel class inherits from D
, browsing toolssuch as those of Visual
Studio .NETwill show A
, B
and C
among its ancestors.
What about creating the corresponding objects? Interfaces don't have instances
or constructors; the objects you'll want to create are instances of the IMPL
classes, created using the appropriate constructors. The solution
is to generate yet another .NET class, called CREATE.A
, for any
non-deferred Eiffel class A
. The generated class contains all the
constructors (Eiffel's creation procedures) for A
, which non-Eiffel
clients will use to create instances of A
. This is easy to explain
in the class documentation.
Note how this architecture takes advantage of various .NET mechanisms. In particular, Impl and Create are .NET namespaces. The result is to export the full power of Eiffel's multiple inheritance to any .NET language, even though the .NET model doesn't support multiple inheritance on its own.
The Language Bus
The implementation of multiple inheritance in Eiffel helps us understand the
relationship between .NET's multilanguage nature and its object model.
At first, the existence of a single object model seems to contradict the claims of language openness. This has led some people to dismiss those claims as hype and state that .NET really imposes a single language, or at least a single set of semantics. This view seems confirmed by the example of managed C++, but the example of multiple inheritance shows that it's wrong. In .NET, languages aren't forced to adopt the common object model; any language can keep its own view of the world, as long as the compiler is able to translate it to that common model. In our original picture above (see "Mapping Languages Into the Object Model"), the responsibility of mapping rests with the compiler, not with the application programmers. It's also the compiler's task to let other languages retain as much as possible of the original view when they access the original software elements.
We've seen this work in the case of multiple inheritance: By cleverly using a common mechanism (multiple inheritance from interfaces), we can give other languages an essentially accurate view of the original inheritance structure, and let them pretend that they support multiple inheritanceeven when they don't.
The Language Bus What the common object model provides is not a stranglehold forcing all languages to support a single view, but a kind of language bus, enabling all languages to cooperate by agreeing on a basic set of common mechanisms. With the multiple inheritance example, we were able to present to the bus, and hence to other languages, a view that doesn't lose any essential property of the original model. |
So what the common object model provides is not a stranglehold forcing all languages to support a single view; instead, it's a kind of language bus, enabling all languages to cooperate by agreeing on a basic set of common mechanisms, as depicted in "The Language Bus" diagram, a reinterpretation of the earlier figure.
Multiple inheritance was a felicitous case, since we were able to present to the bus, and hence to other languages, a view that doesn't lose any essential property of the original model.
Advanced Properties Not Shown to the Rest of the World
We won't always be as fortunate, however, since some high-level constructs may
be impossible to emulate in the object model; for example, genericity. As noted
previously, unmanaged C++ (through its templates) and Eiffel let you define
type-parameterized classes known as generics. Generics ensure type safety.
For example, you can define two LIST
s with elements of type EMPLOYEE
and INTEGER
, respectively:
emplist: LIST [EMPLOYEE] ; intlist: LIST [INTEGER]
The compiler will then accept
emplist.extend (e) and intlist.extend (n) but reject emplist.extend (n)
and intlist.extend (e).
Like Java, the .NET model and, consequently, languages such as C# and VB.NET
do not support genericity: If you want the equivalent of a generic list class,
you let it manipulate values of the most general .NET type, Object, and you
cast back and forth between Object and the actual types needed, such as EMPLOYEE
and INTEGER
. This means that instead of compile-time checks of
a generic language, you must rely on runtime type checksa penalty in both
software reliability and performance.
Here too, as with multiple inheritance, a generic language can continue to
benefit from its own mechanisms and enforce its own rules; however, in this
case, we don't find a ready-made technique to emulate these mechanisms in the
common object model. So when a generic class such as LIST
is made
available to the rest of the .NET world, it will appear exactly as if it were
a nongeneric .NET class, describing lists of Object values.
In the final article, we will discuss how to fully benefit from the language
interoperability promised by .NET. Come back next month for a description of
a key property of .NET, which, surprisingly, most current .NET books don't cover
or even mention: the CLS, or Common Language Specification.
Multiple Languages in .NET
|