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

2024-02-18

在尝试访问者模式和泛型方法时,我发现 C#.NET 中存在某种差异。据我所知,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

然而,此访问者模式实现不允许交换访问者类。引入抽象类 VisitorBase 并将调用转发到重载会产生一些效果。出乎我意料......

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

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


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

EDIT

看起来您可能有 C++ 背景,因此也许值得注意的是,C++ 模板与 C# 泛型有很大不同;特别是,C# 中不存在专门化这样的东西,这可能就是您看到的行为出乎意料的原因。 C# 编译器does not为可以调用泛型方法的不同类型发出不同的代码(也就是说,当您调用泛型方法时,C# 编译器会调用相同的泛型方法)Visit(1) and Visit("hello"),它不会在类型上生成方法的特化int and string)。在运行时,CLR 创建类型特定的方法,但这发生在编译之后,并且不会影响重载解析。

编辑-更多阐述

C# 确实更喜欢非泛型方法而不是泛型方法当非泛型方法静态已知适用时.

C# 编译器将选择一个方法在任何给定的调用站点进行调用。完全忘记重载,并为每个方法指定不同的名称;可以在相关调用站点调用哪些重命名的方法?只有通用的那种。因此,即使三个名称发生冲突并且重载决议启动,这也是该站点适用的唯一重载,并且是所选的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#泛型方法重载与抽象访问者模式不一致 的相关文章

随机推荐