为什么这有效?方法重载 +方法重写 +多态性
在以下代码中:
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 时在运行时调用第二个 MyMethod,而不是陷入无限递归循环?
In the following code:
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
}
}
where AssetDetails is a subclass of AssetBase.
Why does the first MyMethod call the second at runtime when passed an AssetDetails, rather than getting stuck in an infinite loop of recursion?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
C# 将解析您对其他实现的调用,因为对对象上的方法的调用(其中该对象的类有其自己的实现)将优先于重写或继承的方法。
这可能会导致微妙且难以发现的问题,就像您在此处所示的那样。
例如,尝试这段代码(首先读取它,然后编译并执行它),看看它是否达到了您期望的效果。
请注意,如果您将变量的类型声明为
Base
类型而不是Descendant
类型,则调用将转到另一个方法,请尝试更改此行:对此,并重新运行:
那么,您实际上如何调用
Descendant.Test(String)
呢?我的第一次尝试如下所示:
这对我没有任何好处,而是一次又一次地调用
Test(Object)
以防止最终的堆栈溢出。但是,以下有效。因为,当我们将
d
变量声明为Base
类型时,我们最终会调用正确的虚拟方法,因此我们也可以采用这种技巧:这将打印out:
您也可以从外部执行此操作:
将打印出相同的内容。
但首先您需要意识到问题,这完全是另一回事。
C# will resolve your call to your other implementation because calls to a method on an object, where the class for that object has its own implementation will be favored over an overridden or inherited one.
This can lead to subtle and hard-to-find problems, like you've shown here.
For instance, try this code (first read it, then compile and execute it), see if it does what you expect it to do.
Note that if you declare the type of the variable to be of type
Base
instead ofDescendant
, the call will go to the other method, try changing this line:to this, and re-run:
So, how would you actually manage to call
Descendant.Test(String)
then?My first attempt looks like this:
This did me no good, and instead just called
Test(Object)
again and again for an eventual stack overflow.But, the following works. Since, when we declare the
d
variable to be of theBase
type, we end up calling the right virtual method, we can resort to that trickery as well:This will print out:
you can also do that from the outside:
will print out the same.
But first you need to be aware of the problem, which is another thing completely.
请参阅 C# 语言规范中有关成员查找 和重载解析。由于成员查找规则,派生类的重写方法不是候选方法,并且根据重载解析规则,基类方法不是最佳匹配。
第 7.3 节
第 7.4.2 节:
See the section of the C# Language Specification on Member Lookup and Overload Resolution. The override method of the derived class is not a candidate because of the rules on Member Lookup and the base class method is not the best match based on the Overload Resolution rules.
Section 7.3
Section 7.4.2:
正如其他人正确指出的那样,当在类中的两个适用候选方法之间进行选择时,编译器总是选择最初声明的与该类“更接近”的方法。包含检查基类层次结构时的调用站点。
这似乎违反直觉。当然,如果在基类上声明了精确匹配,那么这比在派生类上声明的不精确匹配更好,是吗?
不会。始终选择派生较多的方法而不是派生较少的方法有两个原因。
首先是派生类的作者比基类的作者拥有更多的信息。派生类的作者了解基类和派生类的所有信息,毕竟派生类是调用者实际使用的类。当在调用由无所不知的人编写的方法与仅了解调用者所使用的类型的人编写的方法之间进行选择时,显然优先调用由派生类设计者编写的方法是有意义的。
其次,做出这样的选择会导致某种形式的脆弱基类失败。我们希望保护您免受这种故障的影响,因此编写了重载解析规则,以便尽可能避免这种情况。
有关此规则如何保护您免受脆弱基类故障影响的详细说明,请参阅 我关于该主题的文章。
有关语言处理脆弱基类情况的其他方式的文章,请点击此处 。
As others have correctly noted, when given the choice between two applicable candidate methods in a class, the compiler always chooses the one that was originally declared "closer" to the class which contains the call site when examining the base class hierarchy.
This seems counterintuitive. Surely if there is an exact match declared on a base class then this is a better match than an inexact match declared on a derived class, yes?
No. There are two reasons to choose the more derived method always over the less derived method.
The first is that the author of the derived class has much more information than the author of the base class. The author of the derived class knows everything about both the base class and the derived class, which is, after all, the class that the caller is actually using. When given the choice between calling a method written by someone who knows everything vs someone who knows only something about the type the caller is using, clearly it makes sense to prioritize calling the method written by the designer of the derived class.
Second, making that choice leads to a form of the Brittle Base Class Failure. We wish to protect you from this failure, and therefore have written the overload resolution rules so as to avoid it whenever possible.
For a detailed explanation of how this rule protects you from the Brittle Base Class Failure, see my article on the subject.
And for articles on other ways that languages deal with Brittle Base Class situations, click here.
因为这就是语言的定义方式。对于虚拟成员,当基类和派生类中都存在方法时,在运行时调用的实现基于具体类型< /strong> 调用方法的对象的类型,而不是保存对象引用的变量的声明类型。您的第一个
MyMethod
位于抽象类中。因此它永远不能从MyClass
类型的对象调用——因为这样的对象永远不存在。您可以实例化的只是派生类MySubClass
。具体类型是MySubClass
,因此无论调用它的代码是否在基类中,都会调用该实现。对于非虚拟成员/方法,情况恰恰相反。
Because that's the way the language is defined. For virtual members, the Implementation which is called at runtime, when a method exists in both a base class and a derived class, is based on the concrete type of the object which the method is called against, not the declared type of the variable which holds the reference to the object. Your first
MyMethod
is in an abstract class. So it can never be called from an object of typeMyClass
- because no such object can ever exist. All you can instanitate is derived classMySubClass
. The concrete type isMySubClass
, so that implementation is called, no matter that the code that calls it is in the base class.For non-virtual members/methods, just the opposite is true.