重载parsing和虚拟方法
考虑下面的代码(这有点长,但希望你可以关注):
class A { } class B : A { } class C { public virtual void Foo(B b) { Console.WriteLine("base.Foo(B)"); } } class D: C { public override void Foo(B b) { Console.WriteLine("Foo(B)"); } public void Foo(A a) { Console.WriteLine("Foo(A)"); } } class Program { public static void Main() { B b = new B(); D d = new D (); d.Foo(b); } }
如果你认为这个节目的输出是“Foo(B)”,那么你就和我一样,完全错了! 实际上,它输出“Foo(A)”
如果我从C
类中删除了虚拟方法,那么它就像预期的那样工作:“Foo(B)”是输出。
为什么编译器会select带B
的版本?
答案在C#规范第7.3 节和第7.5.5.1节
我分解了用于select要调用的方法的步骤。
-
首先,构造在T(
T=class D
)中声明的名为N(N=Foo
)的所有可访问成员集合以及T(class C
T=class D
)的基types。 包含覆盖修饰符的声明被排除在外 ( D.Foo(B)被排除 )S = { C.Foo(B) ; D.Foo(A) }
-
构造方法调用的候选方法集合。 从先前成员查找find的与M相关的方法集合开始,将集合简化为适用于参数列表AL(
AL=B
)的那些方法。 集合约简包括对集合中的每个方法TN应用以下规则,其中T(T=class D
)是其中声明方法N(N=Foo
)的types:-
如果N不适用于AL(见第7.4.2.1节 ),则从集合中删除N.
-
C.Foo(B)
适用于AL -
D.Foo(A)
适用于ALS = { C.Foo(B) ; D.Foo(A) }
-
-
如果N适用于AL(见第7.4.2.1节),则所有在T基本types中声明的方法都将从集合中移除 。
C.Foo(B)
被从集合中删除S = { D.Foo(A) }
-
最后的胜利者是D.Foo(A)
。
如果抽象方法从C中删除
如果抽象方法从C中移除,则初始集合是S = { D.Foo(B) ; D.Foo(A) }
S = { D.Foo(B) ; D.Foo(A) }
和重载parsing规则必须用来select该集合中最好的函数成员 。
在这种情况下,获胜者是D.Foo(B)
。
为什么编译器会select带B的版本?
正如其他人所指出的那样,编译器是这样做的,因为这就是语言规范所要做的。
这可能是一个令人无法接受的答案。 一个自然的后续行动将是“什么样的devise原则决定如何指定语言?”
这是一个常见问题,在StackOverflow和我的邮箱。 简短的回答是“这个devise可以缓解脆弱基类的错误家族”。
有关该function的说明及其devise原因,请参阅关于此主题的文章:
http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx
关于各种语言如何处理脆弱基类问题的更多文章,请参阅我关于这个主题的文章的档案:
http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/
这是我对上周同样问题的回答,看起来非常像这个。
为什么在基类中声明的签名被忽略?
这里有三个相关或重复的问题:
C#重载parsing?
方法重载决议和乔恩Skeet的脑筋急转弯
为什么这个工作? 方法重载+方法重载+多态
我认为这是因为在非虚拟方法的情况下使用调用该方法的variables的编译时间types。
你有Foo方法是非虚拟的,因此这个方法被调用。
这个链接有很好的解释http://msdn.microsoft.com/en-us/library/aa645767%28VS.71%29.aspx
所以,下面是它应该如何按照规范工作 (在编译时,并且正确导航文档):
编译器基于方法名称和参数列表,标识typesD
及其基types的匹配方法列表。 这意味着任何一个名为Foo
方法,只要有一个从B
中隐式转换的types的参数,都是有效的候选。 这将产生以下列表:
C.Foo(B) (public virtual) D.Foo(B) (public override) D.Foo(A) (public)
从这个列表中,包含一个覆盖修饰符的任何声明都被排除在外。 这意味着该列performance在包含以下方法:
C.Foo(B) (public virtual) D.Foo(A) (public)
在这一点上,我们有匹配的候选人列表,编译器现在决定要调用什么。 在文档7.5.5.1方法调用中 ,我们find以下文本:
如果N适用于A(见第7.4.2.1节 ),那么所有在T基types中声明的方法都将从集合中移除。
这基本上意味着,如果在D
声明了一个适用的方法,那么基类中的任何方法都将从列表中删除。 在这一点上,我们有一个赢家:
D.Foo(A) (public)
我认为在实现另一个类时,它看起来像树一样,以得到一个方法的可靠实现。 由于没有方法被调用,它正在使用基类。
public void Foo(A a){ Console.WriteLine(“Foo(A)”+ a.GetType()。Name); Console.WriteLine(“Foo(A)”+ a.GetType()。BaseType); }
多数民众赞成在一个猜测我不亲在。净