Создайте динамический тип для симуляции мульти-наследования Python - PullRequest
0 голосов
/ 22 сентября 2019

Я хочу построить динамический тип во время выполнения, чтобы объединить несколько существующих типов в один динамический тип для имитации следующего сценария Python:

class A(object):  
    def Confirm(self):
        print("A")

class B(A):  
    def Confirm(self):
        super().Confirm()
        print("B")

class C(A):  
    def Confirm(self):
        print("C")
        super().Confirm()

class D(A):  
   def Confirm(self):
        print("D")
        super().Confirm()
        print("DD")

class E(B,C,D):  
    pass

Я получил следующие выходные данные:

C
D
A
DD
B

так что стек вызовов учитывает порядок кодов до и после вызова super (), выполняя все коды перед вызовом super (), а затем супер вызов сам, наконец, последняя часть кода, которая следует после вызова super ().Я хочу смоделировать это поведение в c #.

public class A
{
  public virtual void Confirm()
  {
      Console.Writeline("A");
  }
}

public class B : A
{
  public virtual void Confirm()
  {
      base.Confirm();
      Console.Writeline("B");
  }
}

public class C : A
{
  public virtual void Confirm()
  {
      Console.Writeline("C");
      base.Confirm();      
  }
}
public class D : A
{
  public virtual void Confirm()
  {
      Console.Writeline("D");
      base.Confirm();
      Console.Writeline("DD");      
  }
}

public class E : A
{
  public virtual void Confirm()
  {
      // BEFORE base
      Console.Writeline("C"); // C.Confirm() without base, only code before base.Confirm()
      Console.Writeline("D"); // D.Confirm() without base only code before base.Confirm()

      // BASE
      base.Confirm(); // base itself (A)

      // AFTER base
      Console.Writeline("DD"); // return to D.Confirm() only the code after base.Confirm()
      Console.Writeline("B"); // B.Confirm() have only a code after base.Confirm() call
  }
}

Я думаю, что могу решить проблему с помощью System.Reflection.Emit, но я не знаю, возможно ли это.

Ответы [ 2 ]

2 голосов
/ 22 сентября 2019

Ваш вопрос можно перефразировать как «Как мне построить компилятор, который поддерживает множественное наследование и работает на CLR».Если это действительно тот путь, по которому вы хотите пройти, я бы начал здесь и попытался бы прочитать материал VTable, о котором упоминает Брамм.

Существует также этот ТАК вопрос, который показывает, как более или менее MC ++ эмулирует множественное наследование, просто удерживая экземпляры базового класса в качестве членов экземпляров производного типа.Обратите внимание, однако, что в вашем случае вам придется решать проблему алмазов (наследуя от двух или более базовых классов, имеющих общий базовый класс) по-разному, применение этого решения вслепую вызовет трехкратный вызов A.Confirm().Чтобы изменить это, ваш компилятор должен сначала сгладить иерархию наследования, а затем разделить все методы, содержащие вызовы base, на несколько методов.Итак, что-то вроде этого:

public class A
{
  public virtual void Confirm()
  {
      Console.Writeline("A");
  }
}

public class B : A
{
  public virtual void Confirm()
  {
      base.Confirm();
      Console.Writeline("B");
  }

  protected void Confirm_Before1()
  {
  }

  protected void Confirm_After1()
  {
     Console.WriteLine("B");
  }
}

public class C : A
{
  public virtual void Confirm()
  {
      Console.Writeline("C");
      base.Confirm();      
  }

  protected void Confirm_Before1()
  {
      Console.Writeline("C");
  }

  protected void Confirm_After1()
  {
  }
}

public class D : A
{
  public virtual void Confirm()
  {
      Console.Writeline("D");
      base.Confirm();
      Console.Writeline("DD");      
  }

  protected void Confirm_Before1()
  {
      Console.Writeline("D");
  }

  protected void Confirm_After1()
  {
      Console.WriteLine("DD");
  }
}

public class E : A
{
  private readonly B _baseB = new B();
  private readonly C _baseC = new C();
  private readonly D _baseD = new D();

  public virtual void Confirm()
  {
    _baseB.Confirm_Before1();
    _baseC.Confirm_Before1();
    _baseD.Confirm_Before1();

    base.Confirm();

    _baseB.Confirm_After1();
    _baseC.Confirm_After1();
    _baseD.Confirm_After1();
  }
}

Ваш гипотетический компилятор должен будет проанализировать иерархию наследования, найти все методы, доступные из вашего типа с множественным наследованием, разбить их на N + 1 методов, где N - это числоbase вызывает их и вызывает их по порядку в вашем перегруженном методе.

Однако, если вы измените свой пример, все станет намного сложнее.Что если у A есть дополнительный метод Foo, B.Confirm() был:

public virtual void Confirm()
{
   base.Foo();
   Confirm.WriteLine("B");
}

и C.Confirm() был

public virtual void Confirm()
{
    Confirm.WriteLine("C");
    base.Foo();
}

Что вы ожидаете от E.Confirm()?Будет ли он звонить Foo один или два раза?А что, если было несколько вызовов на Foo в B.Confirm(), но не в C.Confirm()?Я мало представляю, как Python справляется с этим, достаточно сказать, что простое изменение класса B на

class B(A):  
    def Confirm(self):
        super().Confirm()
        print("B")
        super().Confirm()

и вызов E.Confirm приводит к

C
D
A
DD
B
C
D
A
DD

Это выглядитпросто сбивает с толку меня, что является частью того, почему MI такой сложный.

В любом случае, окончательный ответ таков: если вам нужно поведение Python-esque, вам придется реализовать компилятор, который ищет нескольконаследование тем или иным способом (скорее всего, с использованием атрибута) и преобразование кода, чтобы сделать именно то, что делает Python.Конкретное решение - далеко не только ответ на вопрос SO, но если у вас возникнут какие-то конкретные проблемы при реализации этой сумасшедшей идеи, не стесняйтесь задавать другую.

РЕДАКТИРОВАТЬ:

Как PSAЯ думаю, что я должен упомянуть: если это не просто забавное упражнение / мысленный эксперимент, который вы делаете, а скорее вы пытаетесь решить реальную проблему - это не тот путь.Перепроектируйте свою систему, чтобы не полагаться на MI, или, если вы любите Python MI, не используйте C #.

1 голос
/ 22 сентября 2019

Я немного переписал питон

class A(object):
    def Confirm(self):
        print("A")

class B(A):
    def Confirm(self):
        print("B.Confirm ENTER")
        super().Confirm() # E forces that it will call C.Confirm()
        print("B.Confirm EXIT")

class C(A):
    def Confirm(self):
        print("C.Confirm ENTER")
        super().Confirm() # E forces that it will call D.Confirm()
        print("C.Confirm EXIT")

class D(A):
    def Confirm(self):
        print("D.Confirm ENTER")
        super().Confirm()
        print("D.Confirm EXIT")

class E(B,C,D):
    pass

e = E()
e.Confirm()

и вот код C # с тем же выводом

using System;

public class Program
{
    public static void Main()
    {
        var e = new E();
        e.Confirm();
    }
}

class A
{
    public virtual void Confirm()
    {
        Console.WriteLine("A");
    }
}

class B : C
{
    public override void Confirm()
    {
        Console.WriteLine("B.Confirm ENTER");
        base.Confirm();
        Console.WriteLine("B.Confirm EXIT");
    }
}

class C : D
{
    public override void Confirm()
    {
        Console.WriteLine("C.Confirm ENTER");
        base.Confirm();
        Console.WriteLine("C.Confirm EXIT");
    }
}

class D : A
{
    public override void Confirm()
    {
        Console.WriteLine("D.Confirm ENTER");
        base.Confirm();
        Console.WriteLine("D.Confirm EXIT");
    }
}

class E : B
{
}

просмотр в реальном времени на .net fiddle

вывод обоих кодов

B.Confirm ENTER
C.Confirm ENTER
D.Confirm ENTER
A
D.Confirm EXIT
C.Confirm EXIT
B.Confirm EXIT
...