重载分辨率和虚拟方法

时间:2010-09-09 06:48:04

标签: c# virtual-method 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);
    }
}

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

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

为什么当A是派生程度较高的类时,编译器会选择采用B的版本?

5 个答案:

答案 0 :(得分:14)

答案在C#规范section 7.3section 7.5.5.1

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

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

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

    • 如果N不适用于AL(Section 7.4.2.1),则从集合中删除N.

      • C.Foo(B)适用于AL
      • D.Foo(A)适用于AL

        S = { C.Foo(B) ; D.Foo(A) }
        
    • 如果N适用于AL(第7.4.2.1节),则从基本类型T中声明的所有方法都将从集合中删除。 C.Foo(B)已从集

      中删除
          S = { D.Foo(A) }
      

最后,获胜者为D.Foo(A)


如果从C

中删除抽象方法

如果从C中删除抽象方法,则初始集为S = { D.Foo(B) ; D.Foo(A) },并且overload resolution rule必须用于选择该集合中的the best function member

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

答案 1 :(得分:9)

  

为什么编译器选择当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/

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

Why are signatures declared in the base class ignored?

以下是三个更相关或重复的问题:

C# overloading resolution?

Method overloads resolution and Jon Skeet's Brain Teasers

Why does this work? Method overloading + method overriding + polymorphism

答案 2 :(得分:1)

我认为这是因为在非虚方法的情况下,使用调用方法的变量的编译时类型。

你有非虚拟的Foo方法,因此调用该方法。

此链接有非常好的解释http://msdn.microsoft.com/en-us/library/aa645767%28VS.71%29.aspx

答案 3 :(得分:1)

所以,这是它应该如何工作according to the specification(在编译时,并且假设我正确地导航了文档):

编译器根据方法名称和参数列表,从类型D及其基类型中识别匹配方法的列表。这意味着任何名为Foo的方法,将B隐式转换类型的一个参数作为有效候选者。这将产生以下列表:

C.Foo(B) (public virtual)
D.Foo(B) (public override)
D.Foo(A) (public)

从此列表中,排除包含覆盖修饰符的任何声明。这意味着该列表现在包含以下方法:

C.Foo(B) (public virtual)
D.Foo(A) (public)

此时我们有匹配的候选列表,编译器现在决定调用什么。在文档7.5.5.1 Method invocations中,我们找到以下文字:

  

如果N适用于A(Section 7.4.2.1),那么在基本类型T中声明的所有方法都将从集合中删除。

这实质上意味着如果在D中声明了一个适用的方法,那么基类中的任何方法都将从列表中删除。在这一点上,我们有一个胜利者:

D.Foo(A) (public)

答案 4 :(得分:0)

我认为在实现另一个类时,它看起来就像树一样可以获得一个方法的可靠实现。由于没有调用方法,因此使用基类。

public void Foo(A a){
    Console.WriteLine("Foo(A)" + a.GetType().Name);
    Console.WriteLine("Foo(A)" +a.GetType().BaseType );
}

这就是猜测我不是.Net的专业人士