2

I have come across a few sources like this that claim multiple inheritance (multiple base types) is actually supported in the CLR (but not in C# and other languages). Based on the method described in this article, it seems it's more of a trick than a direct support, but still I wonder how it is supposed to work.

If creating a custom VTable and using it in a VTFixup actually makes it possible to achieve multiple inheritance, how does one actually implement it in CIL and use it?

IS4
  • 11,945
  • 2
  • 47
  • 86
  • 1
    @Joey Ref classes in C++/CLI are not allowed multiple inheritance, and normal C++ classes don't have any base class (they are value types). – IS4 Mar 07 '19 at 10:29
  • I suppose it is possible, you would have to reverse-engineer how the C++/CLI compiler does it for native C++ classes that are compiled with /clr in effect. No use for the metadata, you only got the MSIL as a guide. – Hans Passant Mar 07 '19 at 13:41
  • 1
    @HansPassant Native C++ classes are compiled as value types, and the correct function is loaded from the C++ vtable via `ldind` and `calli`. – IS4 Mar 07 '19 at 19:18

1 Answers1

1

Currently the CLR does not support multiple inheritance. However, it is proved (look at the standard c++) compiler that a compiler may emulate multiple inheritance even though it only supports single inheritance. Indeed, this is what the MC++ does.

Ideally, you would at least need to:

  • be able to declare multiple inheritance
  • allow the type system to understand it
  • resolve ambiguities when overriding methods
  • handle constructors and finalizers

Multiple Inheritance Emulation

Suppose you want to have the class A that inherits from the classes B1 and B2. Say the classes are:

public class B1
{
    public void MethodDeclaredInB1();
}

public class B2
{
    public void MethodDeclaredInB2();
}

Conceptually, what the compiler (a generic compiler can do) can do the following under the hood: Create a new type, say, A1 which is a simple object with two fields. The code might look like this:

public sealed class A1
{
    public B1 B1;
    public B2 B2;
}

Then, at compile time, transparently transform the calls by accessing the fields:

your high-level code

A a = new A();
a.MethodDeclaredInB1();
a.MethodDeclaredInB2(); 

can be turned in (don't consider the constructor for now):

A1 a = new A1();
a.B1.MethodDeclaredInB1();
a.B2.MethodDeclaredInB2();

Type System Management

This is tough, as the compiler cannot use the standard rules of the language, but needs to use helper methods emitted at compile time to perform the type checking.

your high-level code

Object o = new A();
B1 b = o as B1;
b.MethodDeclaredInB1();

can be turned into

Object o = new A1();
B1 b = AsOperator(o, typeof(B1));
b.MethodDeclaredInB1();

where the AsOperator method could be a general purpose method the does this in pseudocode:

method AsOperator: instance i1 , type t1 -> returns instance of type t1
t2 <- get the runtime type of instance i1
if t2 is not a compiler generated object (e.g. A1) then
    use the standard type system checking (this is trivial and we skip it here)
else
    for each child type c1 in t2->parent classes
        if c1 is subtype of t1 or it is exactly the same as t1 then return the corresponding field (this is a trivial task too) and we are done

    no match, return null

the AsOperator also requires having the CastOperator (which does the same, but instead of returning null, it throws InvalidCastException). these new operators must be spread across the code as the compiler cannot always use static analysis to determine what is the content of an object instance.

Resolve ambiguities when overriding methods This is a pain in the neck as you have to resolve problems like the Diamond Problem. Fortunately, this is a well-known problem, and you can find a solution (at least sub optimal). When invoking inherited and instance methods, the compiler needs to patch the this pointer to target the correct base class.

Handle constructors and finalizers

Constructors are finalizers are particular virtual/inherited methods. In particular, in C++ the type of the object being constructed/destructed changes over time, stopping at the end of the hierarchy. You have to face virtual method calls while constructing/destroying objects even though it is not good practice.

Side Notes

The MC++ compiler emits a value type that uses these concepts and overrides the operators in order to have the required semantics.

Building a compiler that does what you ask is challengingm interesting, but truly hard as you first have to define a proper behaviour for all the new cases (think of the diamond problem for example), while the benefit might be limited. Using interfaces instead of classes, might help in avoiding multiple inheritance.

Yennefer
  • 5,704
  • 7
  • 31
  • 44
  • I am aware there are ways to emulate multiple inheritance, but this is not what I am after. I am interested in the method outlined in the article, or other ways that involve the capabilities of the CLR a bit more. – IS4 Jun 27 '19 at 14:26
  • The article you mentioned has issues for example during verification of the assembly. The fact that the C++ team used the technique you already know (and I wasn't aware you already knew, my bad) is important to me, because even internal MS teams have opted for a more safe technique. Considering the CLR is not technically designed to offer that feature, doing anything in that direction might break your application in the future. – Yennefer Jun 27 '19 at 15:57