.NET中的虚函数
面向对象的程序设计有三大要素,封装、继承和多态。虚函数是多态的重要组成部分,同时又在类的继承关系中有着很多变化。本文讨论.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());
}
By com.microsoft.stbc.devdiv.ndp.interop.dev/mountaintai二世
Comments
Anonymous
August 20, 2008
PingBack from http://housesfunnywallpaper.cn/?p=1651Anonymous
August 20, 2008
面向对象的程序设计有三大要素,封装、继承和多态。虚函数是多态的重要组成部分,同时又在类的继承关系中有着很多变化。本文讨论 .NET 中对虚函数的支持。 首先,我们通过一个例子来看看虚函数的普通用法: classAnonymous
August 31, 2008
VS2008 SP1正式发布: WCF工具的相关功能和贴图 VS2008 SP1正式发布了! 在本篇文章中,我将会用贴图的形式向大家介绍在VS2008 SP1中由我们团队开发的WCF Tools 新功能。