重载分辨率和虚拟方法

Dea*_*ing 22 c# virtual-functions overload-resolution

考虑以下代码(它有点长,但希望你可以遵循):

class A
{
}

class B : A
{
}

class C
{
    public virtual void Foo(B b)
    {
        Console.WriteLine("base.Foo(B)");
    }
}

class D: C
{
    public override void Foo(B b)
    {
        Console.WriteLine("Foo(B)");
    }

    public void Foo(A a)
    {
        Console.WriteLine("Foo(A)");
    }
}

class Program
{
    public static void Main()
    {
        B b = new B();
        D d = new D ();
        d.Foo(b);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你认为这个程序的输出是"Foo(B)"那么你和我在同一条船上:完全错了!事实上,它输出"Foo(A)"

如果我从C类中删除虚方法,那么它按预期工作:"Foo(B)"是输出.

为什么编译器选择带有Awhen 的版本B是派生得更多的类?

Jul*_*rau 15

答案在C#规范第7.3 第7.5.5.1节中

我打破了用于选择要调用的方法的步骤.

  • 首先,构造N=Foo在T(T=class D)中声明的名为N()的所有可访问成员的集合以及T()的基本类型class C.包含覆盖修饰符的声明将从集合中排除(D.Foo(B)是排除的)

    S = { C.Foo(B) ; D.Foo(A) }
    
    Run Code Online (Sandbox Code Playgroud)
  • 构造了方法调用的候选方法集.从与前一个成员查找找到的M相关联的方法集开始,该集合被简化为适用于参数列表AL(AL=B)的那些方法.集合缩减包括将以下规则应用于集合中的每个方法TN,其中T T=class D(N=Foo)是声明方法N()的类型:

最终获胜者是D.Foo(A).


如果从C中删除抽象方法

如果从C中删除抽象方法,则初始集是,S = { D.Foo(B) ; D.Foo(A) }并且必须使用重载决策规则来选择集合中的最佳函数成员.

在这种情况下,获胜者是D.Foo(B).

  • 但是,这不会删除基类型中找到的方法,这是给定参数列表的精确匹配.它认为它更像下面的文本*"如果N适用于A(第7.4.2.1节),那么在基本类型T中声明的所有方法都将从集合中删除."*(这里:http:// msdn .microsoft.com/zh-cn/library/aa691356(VS.71).aspx),描述了此行为的原因.由于"D"中的非虚方法是匹配的,因此删除了基类型中的方法. (3认同)

Eri*_*ert 10

当B是派生更多的类时,为什么编译器会选择带A的版本?

正如其他人所指出的那样,编译器会这样做,因为这就是语言规范所说的.

这可能是一个令人不满意的答案.一个自然的后续行动将是"什么样的设计原则决定以这种方式指定语言?"

这是一个经常被问到的问题,包括StackOverflow和我的邮箱.简短的回答是"这种设计减轻了脆弱的基类系列错误."

有关该功能的说明及其设计原因,请参阅我关于此主题的文章:

http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

有关各种语言如何处理脆弱基类问题的更多文章,请参阅我关于该主题的文章存档:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

这是我对上周同一问题的回答,看起来非常像这一个.

为什么忽略基类中声明的签名?

这里有三个更相关或重复的问题:

C#重载分辨率?

方法超载分辨率和Jon Skeet的Brain Teasers

为什么这样做?方法重载+方法重写+多态