尝试访问者模式和泛型方法我在C#.NET中发现了一种差异。 AFAIK C#编译器更喜欢泛型方法的显式重载,因此代码如下:
public abstract class A
{
public abstract void Accept(Visitor v);
}
public class B : A
{
public override void Accept(Visitor v)
{ v.Visit(this); }
}
public class C : A
{
public override void Accept(Visitor v)
{ v.Visit(this); }
}
public class D : A
{
public override void Accept(Visitor v)
{ v.Visit(this); }
}
public class Visitor
{
public void Visit(B b)
{ Console.WriteLine("visiting B"); }
public void Visit(C c)
{ Console.WriteLine("visiting C"); }
public void Visit<T>(T t)
{ Console.WriteLine("visiting generic type: " + typeof(T).Name); }
}
class Program
{
static void Main()
{
A b = new B();
A c = new C();
A d = new D();
Visitor v = new Visitor();
b.Accept(v);
c.Accept(v);
d.Accept(v);
}
}
产生的输出是(正如预期的那样):
visiting B
visiting C
visiting generic type: D
但是,此访问者模式实现不允许交换Visitor类。引入抽象类VisitorBase并将调用转发给重载会产生smth。对我来说意外......
public abstract class A
{
public abstract void Accept(VisitorBase v);
}
public class B : A
{
public override void Accept(VisitorBase v)
{ v.Visit(this); }
}
public class C : A
{
public override void Accept(VisitorBase v)
{ v.Visit(this); }
}
public class D : A
{
public override void Accept(VisitorBase v)
{ v.Visit(this); }
}
public abstract class VisitorBase
{
public abstract void Visit<T>(T t);
}
public class Visitor : VisitorBase
{
protected void VisitImpl(B b)
{ Console.WriteLine("visiting B"); }
protected void VisitImpl(C c)
{ Console.WriteLine("visiting C"); }
protected void VisitImpl<T>(T t)
{ Console.WriteLine("visiting generic type: " + typeof(T).Name); }
public override void Visit<T>(T t)
{
VisitImpl(t); //forward the call to VisitorImpl<T> or its overloads
}
}
class Program
{
static void Main()
{
A b = new B();
A c = new C();
A d = new D();
VisitorBase v = new Visitor();
b.Accept(v);
c.Accept(v);
d.Accept(v);
}
}
现在输出是:
visiting generic type: B
visiting generic type: C
visiting generic type: D
泛型方法只是偏好通用方法吗?为什么没有调用显式重载?
非常感谢,
Ovanes
答案 0 :(得分:5)
重载是静态完成的,因此当您调用VisitImpl(t)
时,编译器必须选择此调用所代表的单个最佳重载方法(如果有)。由于类型参数T
可以是任何内容,因此唯一兼容的方法是泛型方法,因此Visit<T>(T t)
的所有调用都会调用VisitImpl<T>(T t)
。
修改强>
看起来你可能来自C ++背景,所以也许值得注意的是C ++模板与C#泛型非常不同;特别是,在C#中没有专门化的东西,这可能就是为什么你看到的行为是出乎意料的。 C#编译器不为可以调用泛型方法的不同类型发出不同的代码(也就是说,当您调用Visit(1)
和{{时,C#编译器调用相同的泛型方法1}},它不会在类型Visit("hello")
和int
)生成方法的特化。在运行时,CLR会创建特定于类型的方法,但这会在编译后发生,并且不会影响重载解析。
编辑 - 更详细说明
当静态知道非泛型方法适用时,C#确实更喜欢非泛型方法和泛型方法。
C#编译器将选择一个方法来调用任何给定的调用站点。忘记完全重载,并给你的方法各个不同的名字;可以在相关的呼叫站点调用哪些重命名的方法?只有通用的一个。因此,即使三个名称冲突并且重载决策开始,这是唯一适用于该站点的重载,并且是选择的方法。
答案 1 :(得分:1)
据我所知,我可能非常错误,在编译时,泛型函数访问实际上执行了一种原始类型的拆箱。虽然我们可以逻辑地看到类型应该在编译时运行,但是C#编译器在保存类型时无法通过Visit函数访问VisitImpl函数,因此原始的b.visit(v)在编译时被认为是未装箱的。鉴于此,它必须在调用Visit方法时匹配所有类型的泛型。
编辑:澄清我的意思,因为我只是阅读了自己的废话:
编译器将b.Visit的链接保存为通用调用。它适合并标记为通用。 编译器为Visit-&gt; VisitImpl保存单独的链接,作为必要的类型和/或通用方法。 编译器无法保存来自b.Visit的链接(作为通用) - &gt; VisitImpl是打字的。由于路径来自b.Visit() - &gt; VisitImpl必须通过泛型,它将其作为泛型类型保留,因此通用的VisitImpl是首选。
答案 2 :(得分:1)
看起来你的重载和覆盖太混乱了。
重载是指多个方法 同名,但参数类型不同:
class Foo | +- void Qux(A arg) +- void Qux(B arg) +- void Qux(C arg)
覆盖是指相同(虚拟)方法的多个实现 :
class Foo class Bar : Foo class Baz : Foo | | | +- virtual void Quux() +- override void Quux() +- override void Quux()
C#执行单一调度:
调用方法的重载在编译时确定。
在运行时确定重写方法的实现。
访问者模式通过将方法调用分派给Visit方法的正确实现来利用后者。在具有多个分派的语言中,不需要访问者模式,因为在运行时选择了正确的重载。
答案 3 :(得分:0)
泛型是一种编译器功能,因此只有编译时可用的信息才能用于确定应该调用哪种方法。您正在执行的操作需要在运行时确定变量的实际类型。编译器只知道变量b的类型为A,c的类型为A,而d的类型为A.它选择了最佳的重载,这是一般的重载,因为没有方法需要A。