在以下代码中:
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时会在运行时调用第二个,而不是陷入递归的无限循环中?
答案 0 :(得分:10)
C#将解析对其他实现的调用,因为调用对象上的方法(其中该对象的类具有自己的实现)将优先于被覆盖或继承的实现。
这可能导致细微且难以发现的问题,就像您在此处所示。
例如,尝试这段代码(首先阅读它,然后编译并执行它),看看它是否符合你的预期。
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();
将打印出相同的内容。
但首先 需要了解问题,这完全是另一回事。
答案 1 :(得分:5)
请参阅Member Lookup和Overload Resolution上的C#语言规范部分。由于成员查找的规则,派生类的覆盖方法不是候选方法,并且基类方法不是基于重载决策规则的最佳匹配。
第7.3节
首先,构造在T中声明的名为N的所有可访问(第3.5节)成员的集合,并构造T的基本类型(第7.3.1节)。包含覆盖修饰符的声明将从集合中排除。如果没有名为N的成员存在且可访问,则查找不会产生匹配,并且不评估以下步骤。
第7.4.2节:
这些上下文中的每一个都以其自己独特的方式定义候选函数成员集和参数列表,如上面列出的部分中详细描述的。例如,方法调用的候选集不包括标记为override的方法(第7.3节),如果派生类中的任何方法适用,则基类中的方法不是候选方法(第7.5节) .5.1)。 (强调我的)
答案 2 :(得分:4)
正如其他人已正确指出的那样,当在一个类中选择两个适用的候选方法时,编译器始终选择最初声明“更接近”的那个检查基类层次结构时包含调用站点的类。
这似乎违反直觉。当然,如果在基类上声明了完全匹配,那么这是一个比在派生类上声明的不精确匹配更好的匹配,是吗?
没有。有两个原因选择派生的方法总是比派生得少的方法。
首先,派生类的作者比基类的作者拥有更多的信息。派生类的作者知道基类和派生类的所有内容,毕竟这是调用者实际使用的类。如果在调用由知道所有内容的人编写的方法与仅知道调用者正在使用的类型的某人之间进行选择之间做出选择,那么显然优先调用派生类的设计者编写的方法是有意义的。
其次,做出这种选择会导致一种脆弱的基类失败。我们希望保护您免受此故障的影响,因此编写了重载决策规则,以便尽可能避免使用它。
有关此规则如何保护您免受脆弱基类失败的详细说明,请参阅我关于此主题的文章:
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/
答案 3 :(得分:1)
因为这是定义语言的方式。对于虚拟成员,当基类和派生类中都存在方法时,在运行时调用的实现基于 具体类型 < / strong>调用该方法的对象,而不是保存对象引用的变量的声明的类型。你的第一个MyMethod
是一个抽象类。因此,它可以从MyClass
类型的对象中调用 never - 因为不存在这样的对象。您可以实现的只是派生类MySubClass
。具体类型为MySubClass
,因此无论调用它的代码是否在基类中, 实现都会被调用。
对于非虚拟成员/方法,恰恰相反。