c#泛型方法重载与抽象访问者模式不一致

ova*_*nes 6 c# generics methods overloading operator-precedence

尝试访问者模式和泛型方法我在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

产生的输出(如预期的那样):

visiting B
visiting C
visiting generic type: D
Run Code Online (Sandbox Code Playgroud)

但是,此访问者模式实现不允许交换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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在的输出是:

visiting generic type: B
visiting generic type: C
visiting generic type: D
Run Code Online (Sandbox Code Playgroud)

泛型方法只是偏好通用方法吗?为什么没有调用显式重载?

非常感谢,
Ovanes

kvb*_*kvb 5

重载是静态完成的,因此在调用时VisitImpl(t),编译器必须选择此调用所代表的单个最佳重载方法(如果有).由于type参数T可以是任何东西,唯一兼容的方法是泛型方法,因此所有来自Visit<T>(T t)call的调用VisitImpl<T>(T t).

编辑

看起来你可能来自C++背景,所以也许值得注意的是C++模板与C#泛型非常不同; 特别是,在C#中没有专门化的东西,这可能就是为什么你看到的行为是出乎意料的.C#编译器不会对不同类型的在其中一个泛型方法可以称为发出不同的代码(也就是C#编译器调用,当你调用相同的泛型方法Visit(1)Visit("hello"),它不会在类型生成方法的专业化intstring).在运行时,CLR会创建特定于类型的方法,但这会在编译后发生,并且不会影响重载决策.

编辑 - 更详细的说明

当静态知道非泛型方法适用时, C#确实更喜欢非泛型方法.

C#编译器将选择一个方法来调用任何给定的调用站点.忘记完全重载,并给你的方法各个不同的名字; 可以在相关的呼叫站点调用哪些重命名的方法?只有通用的一个.因此,即使当三个名称发生冲突并且重载决策开始时,这也是唯一适用于该站点的过载,并且是所选择的方法.

  • 这不是一个bug.当编译`Visitor.Visit <T>`时,编译器*没有关于`T`的任何信息*,甚至不是参考类型或值类型.所以编译器别无选择,只能调用`VisitImpl <T>`.请记住,编译器不仅不为通用方法的实例化发出不同的代码,实际上在编译时没有实例化; 只有单一的方法`Visit <T>`. (5认同)

Joe*_*ton 1

据我了解,我可能是非常错误的,在编译时,泛型函数访问实际上执行了一种原始类型的拆箱。虽然我们从逻辑上可以看出类型应该在编译时运行,但 C# 编译器无法在保存类型的同时将 Visit 函数传递到 VisitImpl 函数,因此原始 b.visit(v) 在编译时被视为未装箱。鉴于此,它必须通过泛型来路由调用 Visit 方法时匹配的所有类型。

编辑:为了澄清我的意思,因为我刚刚读了自己的废话:

编译器将 b.Visit 的链接保存为通用调用。它适合并被标记为通用。编译器根据需要将 Visit->VisitImpl 的单独链接保存为类型化和/或泛型方法。编译器无法保存来自 b.Visit(作为通用)-> VisitImpl 的链接(按类型)。由于从 b.Visit() -> VisitImpl 的路径必须经过泛型,因此它将其作为泛型类型保存,因此泛型 VisitImpl 是首选。