【问题标题】:How does polymorphism work with undefined intermediate types in C#?多态性如何处理 C# 中未定义的中间类型?
【发布时间】:2013-03-27 07:29:51
【问题描述】:

在下面的代码中,我预计调用 a.Generate(v) 会导致调用 V.Visit(A a),因为当调用 Generate 时,this 属于 A 类型。然而,似乎 this 被视为Inter 代替。

是否可以在不显式实现AB 中的(相同)方法且仅在共享基类上实现预期行为?如果可以,如何实现?

using System;
using System.Diagnostics;

namespace Test {
    class Base {}
    class Inter: Base {
        public virtual void Generate(V v) {
            // `Visit(Base b)` and not `Visit(A a)` is called when calling
            // A.Generate(v). Why?
            v.Visit(this);
        }
    }

    class A: Inter {}
    class B: Inter {}

    class V {
        public void Visit(Base b) { throw new NotSupportedException(); }
        public void Visit(A a)    { Trace.WriteLine("a"); }
        public void Visit(B b)    { Trace.WriteLine("b"); }
    }

    class Program {
        static void Main() {
            V v = new V();
            A a = new A();
            B b = new B();

            a.Generate(v);
            b.Generate(v);
        }
    }
}

编辑 答案中建议上面的代码不是多态的。我会反对。 V.Visitpolymorphic

【问题讨论】:

  • 嗯...我认为完成这项工作的唯一方法是在 A 和 B 中实现 Generate。
  • 是的...我希望有另一种方式...

标签: c# polymorphism


【解决方案1】:

您期望调用 v.Visit(this) 来确定要根据 v 和 @987654324 的 运行时类型 调用 Visit 的哪个重载@。

具有该功能的语言称为“双重虚拟调度”语言。 (或者,如果考虑多于两件事,它们被称为“多虚拟调度”语言,或“多方法”语言。)

C# 不是双虚拟调度语言;它是一种单虚拟调度语言在编译时做出调度决策时。也就是说,选择哪个重载是根据receiverruntime类型来决定的,但是compile-time类型参数

现在,在您的情况下,C# 不使用 single virtual dispatch 因为对Visit 的调用根本就不是虚拟调用!对Generate 的调用是虚拟调用这一事实完全无关紧要,Visit 过载的事实也无关紧要。对Visit 的分派是非虚拟的,因此分派逻辑完全基于接收者v编译时 类型和参数this。由于已知接收器的类型为V,并且已知参数的类型为Inter,因此重载决议必须选择最佳匹配仅考虑该信息。它无法选择采用ABVisit 版本,因为它们比已知参数类型Inter派生更多。它必须选择具有较少派生形式参数类型的重载,Base

如果您希望在 C# 中实现双虚拟调度,有两种标准方法可以实现。首先可以使用dynamic;使用dynamic,分析是在运行时使用运行时类型执行的。只需将接收器和参数转换为dynamic,编译器就会为您处理好。但是,这会带来巨大的性能成本。

第二种标准方法是使用访问者模式,您可以通过在互联网上搜索找到它。我怀疑根据您的方法名称,您已经尝试实现访问者模式;这不是正确的做法。

【讨论】:

    【解决方案2】:

    这不是多态代码,多态代码具有动态绑定,它决定了在运行时调用哪个方法。两个对象都必须有一个相似的基类和虚拟方法来做这个动态绑定。现在您的绑定是静态的,并且在编译时确定。

    【讨论】:

    • V.Visit 是多态的:en.wikipedia.org/wiki/…
    • V.Visit 不是多态的。这称为操作重载。你的链接甚至告诉你。
    【解决方案3】:

    问题是Generate 方法是在Iter 类中定义的。因此,当调用Generate 方法时,它会将引用传递给Iter

    如果您想传入AB,请将Generate 设为虚拟并在AB 级别覆盖它以传入正确的强制转换值。

    【讨论】:

    • 覆盖AB 正是我想避免的,因为两者中的Generate 方法完全相同。
    • 使Generate 虚拟化不会改变任何东西。
    • 不是 Generate() 已经在原始问题中声明为虚拟的吗?
    • @StephenGross 不,Generate 在原始问题中不是虚拟的。
    • @Randolpho 啊! SO需要更清晰可见的编辑历史的另一个原因:)
    【解决方案4】:

    你到底期待什么?

    public void Visit(Base b) { Trace.WriteLine(b.GetType().Name);  } 
    

    不知何故,编译器为 v.Visit(this) 编译了一个版本的代码。它无法根据传递的参数的真实类型为每个调用实例编译不同的版本。

    在多态设计中,Inter 及其每个派生类负责做“个人”的事情。

    public void Visit(Base b) { b.VisitedBy(v);  } 
    

    其中VisitedBy() 是一个虚函数。

    【讨论】:

    • 我希望Main() 中的代码永远不会调用V.Visit(Base b)
    猜你喜欢
    • 2018-05-16
    • 1970-01-01
    • 1970-01-01
    • 2016-01-10
    • 1970-01-01
    • 1970-01-01
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多