为什么这样做? 方法重载+方法重写+多态

在以下代码中:

public abstract class MyClass { public abstract bool MyMethod( Database database, AssetDetails asset, ref string errorMessage); } public sealed class MySubClass : MyClass { public override bool MyMethod( Database database, AssetDetails asset, ref string errorMessage) { return MyMethod(database, asset, ref errorMessage); } public bool MyMethod( Database database, AssetBase asset, ref string errorMessage) { // work is done here } } 

其中AssetDetails是AssetBase的子类。

为什么第一个MyMethod在传递AssetDetails时会在运行时调用第二个,而不是陷入递归的无限循环?

C#将解析对其他实现的调用,因为调用对象上的方法(其中该对象的类具有自己的实现)将优先于被覆盖或inheritance的实现。

这可能会导致细微且难以发现的问题,就像您在此处所展示的那样。

例如,尝试这段代码(先读取它,然后编译并执行它),看看它是否符合你的预期。

 using System; namespace ConsoleApplication9 { public class Base { public virtual void Test(String s) { Console.Out.WriteLine("Base.Test(String=" + s + ")"); } } public class Descendant : Base { public override void Test(String s) { Console.Out.WriteLine("Descendant.Test(String=" + s + ")"); } public void Test(Object s) { Console.Out.WriteLine("Descendant.Test(Object=" + s + ")"); } } class Program { static void Main(string[] args) { Descendant d = new Descendant(); d.Test("Test"); Console.In.ReadLine(); } } } 

请注意,如果您将变量的类型声明为Base类型而不是Descendant ,则调用将转到另一个方法,尝试更改此行:

 Descendant d = new Descendant(); 

对此,并重新运行:

 Base d = new Descendant(); 

那么,你如何实际设法调用Descendant.Test(String)呢?

我的第一次尝试看起来像这样:

 public void Test(Object s) { Console.Out.WriteLine("Descendant.Test(Object=" + s + ")"); Test((String)s); } 

这对我没有好处,而是一次又一次地调用Test(Object)以获得最终的堆栈溢出。

但是,以下工作。 因为,当我们将d变量声明为Base类型时,我们最终调用正确的虚方法,我们也可以采用这种方法:

 public void Test(Object s) { Console.Out.WriteLine("Descendant.Test(Object=" + s + ")"); Base b = this; b.Test((String)s); } 

这将打印出来:

 Descendant.Test(Object=Test) Descendant.Test(String=Test) 

你也可以从外面做到这一点:

 Descendant d = new Descendant(); d.Test("Test"); Base b = d; b.Test("Test"); Console.In.ReadLine(); 

会打印出来的。

首先需要意识到这个问题 ,这完全是另一回事。

请参阅有关成员查找和过载解决方案的C#语言规范部分。 由于成员查找的规则,派生类的覆盖方法不是候选方法,并且基类方法不是基于重载决策规则的最佳匹配。

第7.3节

首先,构造在T中声明的名为N的所有可访问(第3.5节)成员的集合,并构造T的基本类型(第7.3.1节)。 包含覆盖修饰符的声明将从集合中排除。 如果没有名为N的成员存在且可访问,则查找不会产生匹配,并且不评估以下步骤。

第7.4.2节:

这些上下文中的每一个都以其自己独特的方式定义候选函数成员集和参数列表,如上面列出的部分中详细描述的。 例如,方法调用的候选集不包括标记为override的方法(第7.3节), 如果派生类中的任何方法适用,则基类中的方法不是候选方法 (第7.5.5.1节)。 (强调我的)

正如其他人已正确指出的那样,当在类中选择两个适用的候选方法时,编译器总是在检查基类层次结构时选择最初声明为 “更接近”包含调用站点的类的那个。

这似乎违反直觉。 当然,如果在基类上声明了完全匹配,那么这是一个比在派生类上声明的不精确匹配更好的匹配,是吗?

不会。有两个原因选择派生的方法总是比派生的方法少。

第一个是派生类的作者比基类的作者有更多的信息。 派生类的作者知道基类派生类的所有内容,毕竟,这是调用者实际使用的类。 如果在调用由知道所有内容的人编写的方法与仅知道调用者正在使用的类型的某人之间进行选择之间做出选择,则显然优先调用派生类的设计者编写的方法是有意义的。

其次,做出这种选择会导致一种脆弱的基类失败。 我们希望保护您免受此故障的影响,因此编写了重载决策规则,以便尽可能避免使用它。

有关此规则如何保护您免受脆弱基类失败的详细说明,请参阅我关于此主题的文章:

http://blogs.msdn.com/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

有关语言处理脆弱基类情况的其他方式的文章,请参阅:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

因为这是定义语言的方式。 对于虚拟成员,当基类和派生类中都存在方法时,在运行时调用的实现基于调用该方法的对象的具体类型 ,而不是变量的声明类型。保存对象的引用。 你的第一个MyMethod是一个抽象类。 所以它永远不能从MyClass类型的对象中调用 – 因为不存在这样的对象。 所有你可以实现的是派生类MySubClass 。 具体类型是MySubClass ,因此无论调用它的代码是否在基类中,都会调用实现。

对于非虚拟成员/方法,恰恰相反。