Basically, we will promote classes that are multiply-inherited into .NET interfaces. To give an example if in Eiffel you have:
class C inherit A B end
then in .NET you might have the following set of classes and interfaces:
class A implements IA class B implements IB class C implements IA and IB interface IA interface IB
This is one way of seeing it, but we could generate it in many different ways. It is important that:
Because of the last point, we decided that we would indeed generate the following set of classes and interfaces:
class A_IMP implements A class B_IMP implements B class C_IMP implements A and B interface B interface C
So if in Eiffel you have the following piece of code:
b: B c: C create c b := c
It will translate in C# as:
B b; C c; /* Method 1 */ c = new C_IMP(); b = c; /* Method 2 */ c = EiffelFactory.new_C(); b = c;
Note:
Creation is performed in two different ways. One through the use of a .NET
constructor, and the second one through a factory call. Both will be implemented
in case there is no way to use the .NET constructor mechanism, but we will try
to stick as much as possible on the use of .NET constructor.
Let's suppose we have the following example. In which we do most of what can be seen in an Eiffel library that uses multiple inheritance.
class A feature f is do ... end g (x: X) is do ... end end
class B inherit A rename f as h redefine g end feature g (y: Y) is do ... end end
class C inherit A end
class D inherit B select g end C rename g as C_g select f end end
deferred class F feature f is do ... end end
class E inherit C undefine f end F end
class X end
class Y inherit X end
This is better described by the following BON diagram:
Let's have a look at what we should generate as an inheritance hierarchy:
The hierarchy is much simpler than the first method.
.interface A { f (); g (X x); } .class A_IMP: A { f () { call s_f (Current); }; g (X x) { call s_g (Current, x); }; static s_f (A a) { ... }; static s_g (A a, X x) { ... }; }
.interface B: A { h (); g (Y y); } .class B_IMP: B { h {} { .override A::f call A_IMP::s_f (Current); }; g (Y y) { call s_g (Current, y); }; private __g (X x) { .override A::g call s_g ((Y) x)) } static s_g (B b, Y y) { ... } }
.interface C: A .class C_IMP: C { f () { call A_IMP::s_f (Current); }; g (X x) { call A_IMP::s_g (Current, x); }; }
.interface D: B, C { C_g (X x); } .class D_IMP: D { f() { call A_IMP::s_f (Current); }; h () { call A_IMP::s_f (Current); }; g (Y y) { call B_IMP::s_g (Current, y); }; C_g (X x) { call A_IMP::s_g (Current, x); }; }
.interface F { f(); } .class F_IMP: F { f() { call s_f (Current); }; static s_f (F f) { ... }; }
.interface E: C, F .class E_IMP: E { f() { .override C::f call F_IMP::s_f (Current); }; g(X x) { call A_IMP::s_g (Current, x); }; }
Let's explain the previous example and let's determine what gets generated in the interfaces and what gets implemented in the implementation classes.
In the interface, will be generated the signature of:
Note:
We do not yet take into account local variables for inserting a feature in the
interface.
In the implementation class, we will generate all inherited features (defined in the parent interfaces) as stub to a call to their static definition. The static definition of a feature is generated in the implementation class corresponding to the origin class where the feature is actually written. This avoids to have too many static definitions everywhere that almost look a like.