有一个练习"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)方法?
答案 0 :(得分:3)
这绝对是编译器执行方法解析时会发生的有趣影响。
让我们去specification,看看我们能从中得到什么(这是我的理解,我绝不是阅读规范的专家,所以如果我出错了,请告诉我!)。
第一步是找到参数为Applicable并且第一个子弹读数的方法:
A [Where A是参数列表]中的每个参数对应于函数成员中的参数 声明如对应参数中所述,以及任何 没有参数对应的参数是可选参数。
现在我们来看看Corresponding parameter是什么,我们得到了什么:
对于类中定义的虚方法和索引器,参数 列表是从最具体的声明或覆盖中选取的 函数成员,从接收器的静态类型开始,和 搜索其基类。
我们也有
对于所有其他功能成员和代表,只有一个 参数列表,即使用的参数列表。
因此,对于班级Bar
,我们找到了两种适合该法案的方法:
Bar.Quux(object)
。这是第二段,因为它是直接在类型上定义的。Foo.Quux(int)
。这是从派生类型开始,遵循覆盖virtual
方法声明。对于Baz
课,我们得到3:
Baz.Quux(int[])
。这是在类型上定义的。Bar.Quux(object)
。这是在父类中定义的,并且在Baz
的范围内可见。Foo.Quux(int)
。这是覆盖的虚拟声明。这为我们提供了Bar
中的2个方法匹配,以及Baz
中3个可能的方法匹配。这意味着我们需要使用下面的下一组(Emphasis mine)进一步剔除参数集:
候选方法的集合被简化为仅包含来自的方法 派生类型最多:对于集合中的每个方法C.F,其中C是 声明方法F的类型,在基础中声明的所有方法 从集合中删除C的类型。此外,如果C是类类型 除了object之外,在接口类型中声明的所有方法都是 从集合中删除。 (后一条规则只会影响到 方法组是对类型参数进行成员查找的结果 具有除object和非空的有效基类 有效的界面设置。)
因此,对于Bar
,我们将剔除Foo.Quux(int)
,因为它是在基类型中声明的,因此会从集合中删除。
对于Baz
,我们删除了以下两种方法,因为它们都是在基类型中声明的:
Bar.Quux(object)
Foo.Quux(int)
现在,每个集合只有一个方法,我们可以执行两个方法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)