【问题标题】:Build dynamic type to simulate python multi-inheritance构建动态类型来模拟python多继承
【发布时间】:2020-01-22 17:11:29
【问题描述】:

我想在运行时构建一个动态类型,将多个现有类型组合在一个动态类型中,以模拟以下 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 调用自身,最后是 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 来解决这个问题,但我不知道这是否可行。

【问题讨论】:

  • 在撰写本文时,C# 不支持多继承。
  • 使用call 而不是callvirt。不过,这是一个 hack。
  • 你从来没有向我们展示过产生该输出的调用。当我读到它时,Python 使用了 Diamond Problem Resolution 的“第一个匹配”变体。 D, A, DD 我明白了——它只是 D.Confirm()。但是 C 和 B 怎么会出现在那个名单上呢?在那些确切位置上要少得多?

标签: c# multiple-inheritance cil


【解决方案1】:

您的问题可以改写为“如何构建支持多重继承并在 CLR 上运行的编译器”。如果这真的是你想要走的路,我会开始 here 并尝试阅读 Brumme 提到的 VTable 内容。

还有this SO question,它或多或少地展示了 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 有一个额外的方法FooB.Confirm() 是:

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

C.Confirm()

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

您对E.Confirm() 有什么期望?它会调用Foo 一次或两次吗?如果在B.Confirm() 中多次调用Foo 而在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 式的行为,您必须实现一个编译器,该编译器以一种或另一种方式寻找多重继承(最有可能使用属性)并将代码转换为完全Python 做什么。具体的解决方案远远超出了对 SO 问题的回答,但如果您在实施这个疯狂的想法时遇到任何具体问题,请随时询问另一个问题。

编辑:

作为 PSA,我认为我应该提一下:如果这不仅仅是您正在做的有趣的练习/思想实验,而是您正在尝试解决一个真正的问题 - 这不是正确的方法。重新设计您的系统以不依赖 MI,或者,如果您喜欢 Python MI,请不要使用 C#。

【讨论】:

  • 考虑到所有父类都继承自同一个 A,我没有所有 E 父类的代码来重新编译它,因此我希望创建一种从所有类继承的类型,如果可以的话,我们可以多谈谈我的想法吗?
【解决方案2】:

我稍微重写了python

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.确认 ENTER C.确认 ENTER D.确认回车 一种 D.确认退出 C.确认退出 B.确认退出

【讨论】:

    猜你喜欢
    • 2020-11-11
    • 1970-01-01
    • 2017-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-02
    相关资源
    最近更新 更多