3 回答
TA贡献1803条经验 获得超6个赞
这里需要注意的一件事是,每次都会调用覆盖的版本。将替代更改为:
public override void MyMethod(string s = "bbb")
{
Console.Write("derived: ");
base.MyMethod(s);
}
输出为:
derived: bbb
derived: aaa
类中的方法可以执行以下一项或多项操作:
它定义了其他代码调用的接口。
它定义了一个在调用时执行的实现。
它可能不会两者都做,因为抽象方法只会做前者。
在BBB调用MyMethod()调用方法定义在AAA。
由于中存在覆盖BBB,因此调用该方法将导致实现BBB被调用。
现在,中的定义AAA将两件事告知调用代码(嗯,还有一些无关紧要的地方)。
签名void MyMethod(string)。
(对于支持该语言的语言)单个参数的默认值为"aaa",因此,MyMethod()如果在找不到该方法匹配的形式的形式的代码时MyMethod(),可以用对MyMethod(“ aaa”)的调用来替换它。
因此,这就是调用的BBB作用:编译器看到对的调用MyMethod(),未找到方法,MyMethod()但确实找到了方法MyMethod(string)。它还可以看到在定义它的地方有一个默认值“ aaa”,因此在编译时它将其更改为对的调用MyMethod("aaa")。
从内部BBB,即使在中被覆盖,也AAA被认为AAA是定义方法的地方BBB,以便可以覆盖它们。
在运行时,MyMethod(string)使用参数“ aaa”调用。因为存在一个覆盖的形式,所以该形式被调用,但是不使用“ bbb”来调用它,因为该值与运行时实现无关,而与编译时定义无关。
添加this.更改将检查哪个定义,从而更改调用中使用的参数。
编辑:为什么这对我来说似乎更直观。
就个人而言,由于我所说的是直观的东西,因此只能是个人的,我发现它更直观,原因如下:
如果我正在编码,BBB那么无论是调用还是覆盖MyMethod(string),我都认为这是“正在做的AAA事情”,这是BBB在“正在做的AAA事情” 上做的事情,但实际上AAA都是在做事情。因此,无论是调用还是覆盖,我都将意识到它就是AAA定义的事实MyMethod(string)。
如果我要调用使用过的代码BBB,我会想到“使用BBB东西”。我可能不太了解最初是在中定义的AAA,因此我可能会认为这只是实现细节(如果我也没有使用AAA附近的接口)。
编译器的行为符合我的直觉,这就是为什么当初读这个问题时,我觉得Mono有一个错误。经考虑,我看不到任何一个如何比另一个更好地实现指定的行为。
尽管如此,尽管如此,在保持个人水平的同时,我绝不会将可选参数与抽象,虚拟或覆盖的方法一起使用,并且如果覆盖了别人的方法,我会匹配它们的。
TA贡献1785条经验 获得超8个赞
您可以通过以下方式消除歧义:
this.MyMethod();
(在中MyMethod2())
它是否是一个漏洞是棘手的。它看起来确实不一致。Resharper会警告您根本不要更改覆盖中的默认值,如果这样做会有所帮助; p当然,resharper 还会告诉您this.多余,并愿意为您删除它……这会改变行为-因此,resharper也并不完美。
看起来确实有可能成为编译器错误,我将授予您。我需要看真仔细,以确保...这里的埃里克·当你需要他,是吧?
编辑:
这里的重点是语言规范;让我们看看第7.5.3节:
例如,用于方法调用的候选集不包括标记为重写的方法(第7.4节),并且如果派生类中的任何方法均适用(第7.6.5.1节),则基类中的方法也不是候选者。
(实际上,第7.4节显然没有override考虑方法)
这里有一些冲突...。它指出如果派生类中有适用的方法,则不使用基本方法-这将导致我们进入派生方法,但与此同时,它表示标记override的方法没有考虑过的。
但是,第7.5.1.1节指出:
对于在类中定义的虚拟方法和索引器,参数列表是从函数成员的最特定的声明或重写中选取的,从接收方的静态类型开始,并搜索其基类。
然后第7.5.1.2节解释了在调用时如何评估值:
在函数成员调用(第7.5.4节)的运行时处理期间,参数列表的表达式或变量引用按从左到右的顺序求值,如下所示:
...(剪断)...
当从具有相应可选参数的函数成员中省略参数时,将隐式传递函数成员声明的默认参数。由于这些参数始终是恒定的,因此它们的评估不会影响其余参数的评估顺序。
这明确突出表明它正在查看参数列表,该列表先前在§7.5.1.1中定义为来自最具体的声明或重写。似乎这是第7.5.1.2节中提到的“方法声明”是合理的,因此传递的值应该是从最派生的值到静态类型的值。
这可能表明:csc有一个错误,并且应该使用派生版本(“ bbb bbb”),除非它被限制(通过base.或强制转换为基本类型)查看基本方法声明(第7.6.8节)。 )。
TA贡献1790条经验 获得超9个赞
在我看来,这似乎是个错误。我相信它已被明确指定,并且其行为应与您使用显式this前缀调用该方法的行为相同。
我已将示例简化为仅使用单个虚拟方法,并显示了调用了哪种实现以及参数值是什么:
using System;
class Base
{
public virtual void M(string text = "base-default")
{
Console.WriteLine("Base.M: {0}", text);
}
}
class Derived : Base
{
public override void M(string text = "derived-default")
{
Console.WriteLine("Derived.M: {0}", text);
}
public void RunTests()
{
M(); // Prints Derived.M: base-default
this.M(); // Prints Derived.M: derived-default
base.M(); // Prints Base.M: base-default
}
}
class Test
{
static void Main()
{
Derived d = new Derived();
d.RunTests();
}
}
因此,我们需要担心的是RunTests中的三个调用。前两个调用的规范的重要部分是7.5.1.1节,该节讨论在查找相应参数时要使用的参数列表:
对于在类中定义的虚拟方法和索引器,参数列表是从函数成员的最特定的声明或重写中选取的,从接收方的静态类型开始,并搜索其基类。
以及7.5.1.2节:
当从具有相应可选参数的函数成员中省略参数时,将隐式传递函数成员声明的默认参数。
“相应的可选参数”是将7.5.2关联到7.5.1.1的位。
对于M()和this.M(),参数列表都应该是Derived接收者的静态类型Derived,实际上,您可以告诉编译器将其视为编译早期的参数列表,就好像您在中都将参数强制设置为一样。调用失败-因此调用要求参数在中具有默认值,但随后将其忽略!Derived.M()M()Derived
确实,情况变得更糟:如果您在中提供参数的默认值,Derived但在中将Base其设为必需,则调用 M()最终将null用作参数值。如果没有别的,我想证明这是一个错误:该null值不能来自任何有效的地方。(这是null由于它是string类型的默认值;它始终仅将默认值用于参数类型。)
与base.M(),它表示,该规范交易部分7.6.8 ,以及作为非虚拟行为,表达被视为((Base) this).M(); 因此,使用基本方法确定有效参数列表是完全正确的。这意味着最后一行是正确的。
只是为了使想要查看上述真正奇怪的错误的人更轻松,其中使用了未在任何地方指定的值:
using System;
class Base
{
public virtual void M(int x)
{
// This isn't called
}
}
class Derived : Base
{
public override void M(int x = 5)
{
Console.WriteLine("Derived.M: {0}", x);
}
public void RunTests()
{
M(); // Prints Derived.M: 0
}
static void Main()
{
new Derived().RunTests();
}
- 3 回答
- 0 关注
- 641 浏览
添加回答
举报