打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
.NET中的虚函数
userphoto

2013.12.18

关注
面向对象的程序设计有三大要素,封装、继承和多态。虚函数是多态的重要组成部分,同时又在类的继承关系中有着很多变化。本文讨论.NET中对虚函数的支持。
首先,我们通过一个例子来看看虚函数的普通用法:
class CA {
public virtual void Foo() {
Console.WriteLine("CA.Foo");
}
}
class CB : CA  {
public override void Foo() {
Console.WriteLine("CB.Foo");
}
}
class Test  {
public static void InvokeFoo(CA ca)   {
ca.Foo();
}
public static void Main()    {
InvokeFoo(new CB());
}
}
输出结果
CB.Foo
在这个例子中,尽管在调用InvokeFoo()的时候,CB被转换成CA,但是当执行ca.Foo的时候,仍然调用了CB的Foo。因为ca此时指向的是一个CB类型的对象。这种调用模式,我们称之为运行时绑定。因为在编译InvokeFoo时,编译器无法获取参数ca的真实类型,只有在运行的时候,才能根据ca的真实类型,决定调用哪一个函数。
在这个例子中,两个关键字值得我们注意,首先是virtual,他告诉编译器,当前函数需要运行时绑定。其次是override,他告诉编译器,我要覆盖基类中的Foo()。
看到这里,可能读者会对两个问题持有疑惑:
[问题]: 不用virtual结果如何?
[问题]: 不用override结果如何?
读者不妨自己动手修改上例,尝试这两个关键字的不同组合,看看输出的结果如何。在这里,我仅给出组合条件和其输出结果。
序号
基类(CA)中是否有virtual
子类(CB)中是否有override
输出
1
CB.Foo
2
CA.Foo
3
编译错误
4
CA.Foo
我希望通过对这组实验结果的解释,交待一些.NET中虚函数的相关概念。
运行时绑定仅体现在虚函数中。因此在试验4中,输出的结果是CA.Foo。因为Foo没有被申明为virtual,在编译阶段,已经把ca.Foo绑定到CA.Foo。
Override只能用于虚函数中。当子类继承基类,他便拥有了基类所有的函数,Override修饰的函数,将替换基类原来的函数。否则,子类会新增加一个函数,并同时保留基类中的函数。 下面的这个例子,很好的说明了这个问题
class CA  {
public virtual void Foo()  {
Console.WriteLine("CA.Foo");
}
}
class CB : CA  {
public override void Foo()   {
Console.WriteLine("CB.Foo");
}
}
class CC : CA   {
public new void Foo()   {
Console.WriteLine("CC.Foo");
}
}
class Test   {
public static void Main()   {
Console.WriteLine(typeof(CB).GetMethods().Length);   // 输出5
Console.WriteLine(typeof(CC).GetMethods().Length);   // 输出6
}
}
这段程序输出CB和CC的函数个数,CB的5个函数中,4个来自于Sysetm.Object,剩下的一个就是Foo。CC中多了一个函数,因为使用了new (如果不使用new,也是相同的结果,因为C#编译器默认使用new,但不显示指明new会给出一个警告),说明了CC.Foo是一个不同于CA.Foo的虚函数。
所以,在试验2中,不使用override,我们在InvokeFoo中调用的还是CA.Foo()。虽然这个时候还是运行时绑定,但是因为CB.Foo并没有覆盖CA.Foo,因此我们还是得到了基类的实现。
当一个函数不是虚函数的时候,子类中相同签名的函数总是覆盖了父类中的函数,并不需要override关键字。所以c#编译器会把它当作一个错误,如上表中试验3所示。
如果读者理解了上面的内容,那么来看看一个略微复杂的情况:我们邀请interface出场!
interface IA   {
void Foo();
}
class CA: IA  {
public void Foo()  {
Console.WriteLine("CA.Foo");
}
}
[问题]: Foo是虚函数吗?
答案是肯定的,就像interface方法不能显示声明为public一样,我们也不能在IA.Foo前面加上virtual。原因很简单,所有的interface方法都是虚函数!在调用interface方法的时候,总是要使用运行时绑定。
[问题]: CA实现IA,那么CA.Foo前面需要override吗?
答案是否定的,在C#中,继承和实现是截然不同的两个概念,尽管在语法上很相似。继承意味着全盘接收基类的函数,而实现只是一个契约,保证当前类会提供interface中声明的函数,而不会接受基类的函数(事实上也不能,因为interface中没有函数的实现)
[问题]: CA实现IA,那么CA.Foo前面需要virtual吗?
答案是需要的,否则的话,CA的子类将无法覆写Foo,下面的代码是CA.Foo的IL声明,我们发现了关键字final(注:这里的final是IL语言的关键字,和C#中sealed有些类似,意味着子类不能override当前函数)
.method public hidebysig newslot virtual final
instance void  Foo() cil managed
下面一段代码紧接着上面的代码,读者可以猜测一下输出,看看是否掌握了本文今天讲述的内容,我会在下期博客中讲解其原委,并且和大家进一步通过IL来研究.NET中的虚函数。
class CB : CA, IA  {
public void Foo()   {
Console.WriteLine("CB.Foo");
}
}
class Test   {
public static void InvokeFoo(CA ia)  {
ca.Foo();
}
public static void Main()   {
InvokeFoo(new CA());
InvokeFoo(new CB());
}
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C#基础概念二十五问
c#中的多态
.net面试题3
c#关键字
C#基础方法和要点
从未来看 C#
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服