Как использовать множественное наследование в CLR? - PullRequest
1 голос
/ 07 марта 2019

Я сталкивался с несколькими источниками, такими как this , которые утверждают, что множественное наследование (несколько базовых типов) фактически поддерживается в CLR (но не в C # и других языках).Основываясь на методе, описанном в этой статье, кажется, что это больше уловка, чем прямая поддержка, но все же мне интересно, как это должно работать.

Если создание собственного VTable и его использование в VTFixup на самом делепозволяет достичь множественного наследования, как на самом деле реализовать его в CIL и использовать его?

1 Ответ

0 голосов
/ 27 июня 2019

В настоящее время CLR не поддерживает множественное наследование. Однако доказано (посмотрите на стандартный c ++) компилятор, что компилятор может эмулировать множественное наследование, даже если он поддерживает только одиночное наследование. Действительно, это то, что делает MC ++.

В идеале вам, по крайней мере, нужно:

  • сможет объявить множественное наследование
  • позвольте системе типов понять это
  • разрешить неоднозначности при переопределении методов
  • обрабатывать конструкторы и финализаторы

эмуляция множественного наследования

Предположим, вы хотите иметь класс A , который наследуется от классов B1 и B2 . Скажем, классы:

public class B1
{
    public void MethodDeclaredInB1();
}

public class B2
{
    public void MethodDeclaredInB2();
}

Концептуально, что компилятор (универсальный компилятор может сделать) может сделать следующее под капотом: Создайте новый тип, скажем, A1 , который представляет собой простой объект с двумя полями. Код может выглядеть так:

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

Затем во время компиляции прозрачно преобразуйте вызовы, открыв поля:

ваш код высокого уровня

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

можно включить (пока не рассматривайте конструктор):

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

Тип системы управления

Это сложно, так как компилятор не может использовать стандартные правила языка, но ему нужно использовать вспомогательные методы, генерируемые во время компиляции, для выполнения проверки типа.

ваш код высокого уровня

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

можно превратить в

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

, где метод AsOperator может быть методом общего назначения, который делает это в псевдокоде:

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

AsOperator также требует наличия CastOperator (который делает то же самое, но вместо возврата null выдает InvalidCastException). эти новые операторы должны быть распределены по всему коду, так как компилятор не всегда может использовать статический анализ для определения содержимого экземпляра объекта.

Устранить неоднозначности при переопределении методов Это боль в шее, так как вам приходится решать такие проблемы, как Diamond Problem . К счастью, это хорошо известная проблема, и вы можете найти решение (по крайней мере, субоптимальное). При вызове унаследованных методов и методов экземпляра компилятору необходимо исправить указатель this, чтобы указать правильный базовый класс.

Обработка конструкторов и финализаторов

Конструкторы являются финализаторами конкретных виртуальных / унаследованных методов. В частности, в C ++ тип создаваемого / разрушаемого объекта со временем изменяется, останавливаясь в конце иерархии. При создании / уничтожении объектов вам приходится сталкиваться с вызовами виртуальных методов, даже если это не очень хорошая практика.

Примечания стороны

Компилятор MC ++ генерирует тип значения, который использует эти понятия и переопределяет операторы, чтобы иметь требуемую семантику.

Создание компилятора, выполняющего то, что вы просите, является интересным, но по-настоящему трудным делом, поскольку сначала вы должны определить правильное поведение для всех новых случаев (например, подумайте о проблеме алмазов), хотя выгода может быть ограничена. Использование интерфейсов вместо классов может помочь избежать множественного наследования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...