在下面的代码中,我原本希望调用a.Generate(v)
会导致调用V.Visit(A a)
,因为当调用Generate时,this
的类型为A. Hoewever,它似乎{ {1}}被视为this
。
是否可以在Inter
和A
中明确实现(相同)方法并且仅在共享基类上实现预期的行为?如果是这样,怎么能实现呢?
B
修改
在答案中建议上面的代码不是多态的。我会反对。 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);
}
}
}
为polymorphic。
答案 0 :(得分:15)
您希望调用v.Visit(this)
根据的运行时类型确定要调用Visit
的重载 {{ 1}}和v
。
具有该功能的语言称为"双重虚拟调度"语言。 (或者,如果考虑两个以上的东西,他们被称为"多个虚拟派遣"语言,或"多方法"语言。)
C#不是双虚拟派遣语言;当在编译时做出调度决策时,它是单虚拟调度语言。也就是说,决定选择哪个重载是基于 receiver 的 runtime 类型,而编译时类型参数。
现在,在您的情况下,C#不使用单个虚拟调度,因为对this
的调用甚至不是虚拟调用!调用Visit
是虚拟调用这一事实完全无关紧要,Generate
过载的事实也无关紧要。调度到Visit
非虚拟,因此调度逻辑完全基于接收器的编译时类型Visit
和参数v
。由于已知接收器属于this
类型,并且已知参数类型为V
,因此重载决策必须选择最佳匹配仅给出该信息。它无法选择Inter
或Visit
的{{1}}版本,因为它们比已知参数类型A
更多派生。它必须使用 less derived 形式参数类型B
选择重载。
如果您希望在C#中实现双虚拟调度,有两种标准方法可以实现。首先,您可以使用Inter
;使用Base
,分析在运行时使用运行时类型执行。只需将接收器和参数强制转换为dynamic
,编译器就会为您处理它。但这会带来显着的性能成本。
第二种标准方法是使用访客模式,您可以通过在互联网上搜索来找到它。我怀疑你的方法名称是否已经尝试实现访问者模式;这不是正确的做法。
答案 1 :(得分:2)
问题是Generate
类中定义了Iter
方法。因此,当调用Generate
方法时,它会将引用传递给Iter
。
如果您要传递A
或B
,请Generate
虚拟并在A
和B
级别覆盖它以正确传递铸造价值。
答案 2 :(得分:2)
这不是多态代码,多态代码具有动态绑定,它决定了在运行时调用哪个方法。两个对象必须具有类似的基类和虚拟方法才能执行此动态绑定。现在你的绑定是静态的,并在编译时确定。
答案 3 :(得分:1)
您对此有何期待?
public void Visit(Base b) { Trace.WriteLine(b.GetType().Name); }
不知何故,编译器为v.Visit(this)编译了一个版本的代码。根据传递的参数的实际tpye,它无法为每个调用实例编译不同的版本。
在多态设计中,Inter和它的每个派生类都负责做“个人”的事情。
public void Visit(Base b) { b.VisitedBy(v); }
其中VisitedBy()
是虚函数。