访问重写方法

时间:2018-02-11 18:23:57

标签: c# oop override overloading

有一个练习"OverloadResolutionOverride"

以下代码的输出结果如何:

class Foo
{
  public virtual void Quux(int a)
  {
    Console.WriteLine("Foo.Quux(int)");
  }
}
class Bar : Foo
{
  public override void Quux(int a)
  {
    Console.WriteLine("Bar.Quux(int)");
  }
  public void Quux(object a)
  {
    Console.WriteLine("Bar.Quux(object)");
  }
}
class Baz : Bar
{
  public override void Quux(int a)
  {
    Console.WriteLine("Baz.Quux(int)");
  }
  public void Quux<T>(params T[] a)
  {
    Console.WriteLine("Baz.Quux(params T[])");
  }
}
void Main()
{
  new Bar().Quux(42);
  new Baz().Quux(42);
}

答案是:

Bar.Quux(object)
Baz.Quux(params T[])

网站上有explanation

  

如果编译器为方法找到合适的签名   在“当前”类中调用,编译器不会向父母查找   类。

是否认为重载的Quux(int)方法在基类中,而不是在当前的方法中?如果是这样,我如何在当前类中调用Quux(int)方法?

3 个答案:

答案 0 :(得分:3)

这绝对是编译器执行方法解析时会发生的有趣影响。

让我们去specification,看看我们能从中得到什么(这是我的理解,我绝不是阅读规范的专家,所以如果我出错了,请告诉我!)。

第一步是找到参数为Applicable并且第一个子弹读数的方法:

  

A [Where A是参数列表]中的每个参数对应于函数成员中的参数   声明如对应参数中所述,以及任何   没有参数对应的参数是可选参数。

现在我们来看看Corresponding parameter是什么,我们得到了什么:

  

对于类中定义的虚方法和索引器,参数   列表是从最具体的声明或覆盖中选取的   函数成员,从接收器的静态类型开始,和   搜索其基类。

我们也有

  

对于所有其他功能成员和代表,只有一个   参数列表,即使用的参数列表。

因此,对于班级Bar,我们找到了两种适合该法案的方法:

  1. Bar.Quux(object)。这是第二段,因为它是直接在类型上定义的。
  2. Foo.Quux(int)。这是从派生类型开始,遵循覆盖virtual方法声明。
  3. 对于Baz课,我们得到3:

    1. Baz.Quux(int[])。这是在类型上定义的。
    2. Bar.Quux(object)。这是在父类中定义的,并且在Baz的范围内可见。
    3. Foo.Quux(int)。这是覆盖的虚拟声明。
    4. 这为我们提供了Bar中的2个方法匹配,以及Baz中3个可能的方法匹配。这意味着我们需要使用下面的下一组(Emphasis mine)进一步剔除参数集:

        

      候选方法的集合被简化为仅包含来自的方法   派生类型最多:对于集合中的每个方法C.F,其中C是   声明方法F的类型,在基础中声明的所有方法   从集合中删除C的类型。此外,如果C是类类型   除了object之外,在接口类型中声明的所有方法都是   从集合中删除。 (后一条规则只会影响到   方法组是对类型参数进行成员查找的结果   具有除object和非空的有效基类   有效的界面设置。)

      因此,对于Bar,我们将剔除Foo.Quux(int),因为它是在基类型中声明的,因此会从集合中删除。

      对于Baz,我们删除了以下两种方法,因为它们都是在基类型中声明的:

      1. Bar.Quux(object)
      2. Foo.Quux(int)
      3. 现在,每个集合只有一个方法,我们可以执行两个方法Bar.Quux(object)Baz.Quux<int>(int[])

        选择正确的方法

        所以这引出了一个问题,我们可以强制调用正确的方法吗?并且基于第二个解决步骤,它使用最派生的类型,答案就是我们可以。

        如果我们想调用Foo的方法,我们需要将调用者的类型设置为Foo。如果我们希望Baz调用Bar的方法,那么我们需要将Baz设置为Bar

        考虑以下方法调用:

        new Bar().Quux(42);
        new Baz().Quux(42);
        ((Foo)new Bar()).Quux(42);
        ((Foo)new Baz()).Quux(42);
        ((Bar)new Baz()).Quux(42);
        

        我们得到以下输出:

        Bar.Quux(object)
        Baz.Quux(params T[])
        Bar.Quux(int)
        Baz.Quux(int)
        Bar.Quux(object)
        

        并且能够针对我们想要使用与上述类似的方法解决方案的特定方法。

        再多一步

        如果我们将Baz的定义更改为:

        class Baz : Bar
        {
          public override void Quux(int a)
          {
            Console.WriteLine("Baz.Quux(int)");
          }
        }
        

        然后调用方法:new Baz().Quux(42);我们的输出仍为:Bar.Quux(object)。这看起来很奇怪,因为我们直接在Baz内定义了一个方法覆盖。但是,该方法的原始类型为Foo,其具体程度低于Bar。因此,当我们匹配参数列表时,我们最终得到Bar.Quux(object)Foo.Quux(int),因为int参数列表是在Foo上定义的。因此,Foo.Quux(int)在第二步中被淘汰,因为Bar的派生程度高于Foo,我们称之为Bar.Quux(object)

        我认为这里故事的寓意是不要将方法命名为覆盖!

答案 1 :(得分:2)

另外注意,调用你不想要的方法的方法是向下转发:(Bar)(new Baz).Quux(42);

为什么编译器会选择泛型方法?在解决方法解析时,不是优先于通用方法的非通用匹配吗?

是的,但另一条规则也适用于此;方法解析将优先使用 nearest 适用的方法,最接近的方法是声明与调用点相关的方法。

嗯... Quux(int)Baz中宣布,所以我不确定......

没有! Overriden方法属于实现虚方法的类;在这种情况下,就编译器而言,&#34;所有者&#34; Quux的{​​{1}}是Foo,而不是Baz

为什么编译器会应用这样的规则?好吧,假设以下情况:

Alpha公司发布了一个班级A

class A { ... }

公司测试版通过A进行扩展,继续消费B

class B: A { 
    public void Frob<T>(T frobbinglevel) { ... }
    ... }

一切都是骄傲的,并且Beta凭借其精彩的表现取得了巨大的成功。 Alpha公司决定自行开展业务并发布新版A

class A { 
    public virtual void Frob(int frobbinglevel) { ... }
    ... }

Beta使用Alpha的更新重新编译其库,现在应该发生什么?如果编译器开始选择A.Frob(1)超过B.Frob(1)A.Frob是非通用的完全匹配吗?

但是嘿...... Beta没有改变任何东西,现在它的班级没有按预期运行!突然间,在没有任何代码更改的情况下,方法解析就是选择不同的方法,打破客户......这看起来并不合适。这就是规则存在的原因。

答案 2 :(得分:1)

要从Bar或Baz调用Quux(int),你应该将Bar或Baz转换为Foo:

Foo bar = new Bar();
Foo baz = new Baz();
bar.Quux(42); 
baz.Quux(42);

输出:

Bar.Quux(int)
Baz.Quux(int)