代码之家  ›  专栏  ›  技术社区  ›  Dean Harding

过载分辨率和虚拟方法

  •  21
  • Dean Harding  · 技术社区  · 14 年前

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

    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 回复  |  直到 6 年前
        1
  •  14
  •   Wai Ha Lee captain-yossarian from Ukraine    6 年前

    答案在C规范中。 section 7.3 section 7.5.5.1

    我分解了用于选择要调用的方法的步骤。

    • 首先,名为n的所有可访问成员的集合( N=Foo )以t表示( T=class D )以及T的基型( class C )是构造的。 包含重写修饰符的声明从集合中排除。 ( 不包括D.foo(b) )

      S = { C.Foo(B) ; D.Foo(A) }
      
    • 构造方法调用的候选方法集。从上一个成员查找找到的与m关联的一组方法开始,该组被简化为适用于参数列表al的那些方法。( AL=B )集合约简包括对集合中的每个方法t.n应用以下规则,其中t( D类 )是方法n的类型( n=fo )声明:

      • 如果n不适用于al( Section 7.4.2.1 ,则n从集合中移除。

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

          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) .

        2
  •  9
  •   Community CDub    7 年前

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

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

    这可能是一个令人不满意的答案。一个自然的后续行动将是“在决定以这种方式指定语言的基础上,设计原则是什么?”

    在stackoverflow和我的邮箱中,这是一个常见的问题。简单的答案是“这种设计减轻了脆弱的基础类bug家族。”

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

    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

        3
  •  1
  •   Unmesh Kondolikar    14 年前

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

    您有一个非虚拟的foo方法,因此调用了该方法。

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

        4
  •  1
  •   Fredrik Mörk    14 年前

    所以,这就是它的工作原理 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)
    
        5
  •  0
  •   Kieran    14 年前

    我认为,当实现另一个类时,它会在树的最上面查找方法的可靠实现。因为没有调用方法,所以它正在使用基类。

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

    我想我在.net上不是职业选手