具体的措辞和位置因规范的不同版本而异,但例如here可以阅读:
构造方法调用的候选方法集。从与 M 相关联的一组方法开始,这些方法是通过先前的成员查找(第 7.3 节)找到的,该集合被缩减为适用于参数列表 A 的那些方法。集合缩减包括应用以下规则到集合中的每个方法 TN,其中 T 是声明方法 N 的类型:
如果 N 不适用于 A(第 7.4.2.1 节),则从集合中删除 N。
如果 N 适用于 A(第 7.4.2.1 节),则在 T 的基类型中声明的所有方法都将从集合中删除。
所以,假设我们有DerivedClass 类型的obj,那么成员方法集包含来自DerivedClass 的void SomeMethod(long) 和来自BaseClass 的void SomeMethod(int)。
这两种方法都适用,确实void SomeMethod(int)是更好的重载匹配,但是因为上面引用的最后一句中的规则,一旦发现void SomeMethod(long)适用,所有来自基类的方法都是从候选集中删除,这意味着不再考虑 void SomeMethod(int)。
好的,这是规范方面的技术原因。首先在规范中的设计原因是什么?
好吧,想象一下 BaseClass 开始定义为:
public class BaseClass
{
}
如果其余代码相同,那么很明显对 obj.SomeMethod(5) 的调用应该调用唯一存在的所谓方法。
现在考虑如果在该代码被编写之后,void SomeMethod(int) 方法被添加到BaseClass。并且确实考虑到这可能与DerivedClass 在不同的程序集中,并且由单独的作者编写。
现在调用SomeMethod() 的含义已经改变。更糟糕的是,它是否更改取决于给定机器已应用或未应用哪些更新。 (更糟糕的是,由于 C# 重载解析中不使用返回类型,它的更改方式可能会在已编译的代码中产生编译错误:完全破坏性更改。
如果存在来自更多派生类的重载候选者,则排除基类中定义的方法的规则允许更大的保证,即在面对未来的变化时,人们正在调用想要调用的方法。 (当然,如果您打算调用基类方法,您可能会感到惊讶,但是在编码时您可能会发现该问题并使用强制转换来确保您想要的行为是结果)。
这可能会让一些人感到惊讶的结果是:
class Program
{
static void Main(string[] args)
{
var obj = new DerivedClass();
obj.SomeMethod(5);
}
}
class BaseClass
{
public virtual void SomeMethod(int a) { Console.WriteLine("Base"); }
}
class DerivedClass : BaseClass
{
public override void SomeMethod(int a) { Console.WriteLine("Defined in Base, overriden in Derived"); }
public void SomeMethod(long a) { Console.WriteLine("Derived"); }
}
这将输出Derived,因为此规则根据声明方法的位置应用,即使存在来自覆盖的实现。
(该规则正常工作的另一个原因是,当它转换为 CIL 时,调用将包含有关其声明的类的信息。这里的规则是最简单的做事方式。也就是说;1)在 CIL 的设计中应用了类似的逻辑,并且 2)上述使 CIL 的特性成为 C# 人员可以使用的特性,而不是与之对抗的特性)。