频 道 直 达 - 新闻 - 培训 - 软件 - 教程 - 前沿 - 组网 - 系统应用 - 安全 - 编程 - 存储 - 操作系统 - 数据库 - 服务器 - 专题 - 产品 - 案例库 - 读书 - 博客 - BBS
51CTO.COM_中国最大的网络技术网站
找资料:

6.6.1 CLR如何调用虚方法、属性和事件

作者: Richter,J.著/周靖,张杰良 译 出处:清华大学出版社  2008-01-10 12:20    砖    好    评论   进入论坛
阅读提示:《框架设计》(第2版)本书主要介绍了CLR Via C#的知识与技术。第六章主要介绍的是类型的可见性,成员的种类和可访问性等内容。本文主要说的是CLR如何调用虚方法、属性和事件。

6.6.1  CLR如何调用虚方法、属性和事件

本节将讨论方法,但是本节中的讨论也与虚属性(virtual property)和虚事件(virtual event)相关。属性和事件实际上是作为方法实现的,这将在相应的章节进行示范。

方法表示在类型(静态方法)或者类型的实例(非静态方法)上执行操作的代码。所有的方法都有名称、签名和返回值(可能为void)。CLR允许类型定义多个名称相同的方法,只要这些方法具有不同的参数集或者不同的返回值。因此可以定义两个具有相同名称、相同参数的方法,只要这两个方法的返回值类型不同。但是,除了IL汇编语言外,好像没有其他语言具有该“特征”,大多数语言(包括C#语言在内)在确定惟一性时,要求方法的参数不同,而忽略方法返回值的类型。(C#在定义转换操作符方法时实际上放松了此限制,详见第8章的介绍。)
下面示范的Employee类定义了3个不同种类的方法:

internal class Employee {
//非虚实例方法
    public Int32 GetYearsEmployed() { ... }

    //虚方法(虚拟隐含着实例)
    public virtual String GenProgressReport() { ... }

    //静态方法
    public static Employee Lookup(String name) { ... }
} 

当编译器编译上述代码时,编译器在最后得到的程序集的方法定义表中写入三个条目,每个条目都有一个标记来表明方法是实例、虚的还是静态的。

所编写的代码调用这些方法时,编译器生成的调用代码检查方法定义的标记,以此来确定如何生成正确的IL代码,以便正确进行调用。CLR为方法的调用提供了以下两个IL指令:

IL指令call可以用来调用静态方法、实例方法和虚方法。使用call指令调用静态方法时,必须指定CLR要调用的方法的类型。使用call指令调用实例方法或者虚方法时,必须指定变量来引用对象。call指令假定变量不为null,换句话说,也就是变量本身的类型指出了用什么类型定义CLR要调用的方法。如果变量的类型没有定义方法,则检查基础类型来匹配方法。指令call通常用来非虚拟地调用虚方法。

IL指令callvirt用来调用实例方法和虚方法,而不能调用静态方法。使用callvirt指令调用实例方法或者虚方法时,必须指定变量来引用对象。使用IL指令callvirt指令调用非虚实例方法时,变量的类型指出了用什么类型定义CLR要调用的方法。使用IL指令callvirt调用虚实例方法时,CLR查找用来调用的对象的实际类型,然后多形式地调用方法。为了决定类型,用来调用的变量通常不能为null,换句话说,也就是编译该调用时,JIT编译器生成验证变量是否为null的代码,如果变量为null,callvirt指令引发CLR抛出一个NullReferenceException异常。这种额外的检查意味着IL指令callvirt的执行速度比call指令稍慢。注意,即使callvirt指令用来调用非虚实例方法时,也要执行这种变量是否为null的检查。

现在,我们将这两个调用指令放在一起,看看C#是如何使用这两个不同的IL指令的:

using System; 
 
public sealed class Program{ 
    public static void Main(){ 
        Console.WriteLine();//调用一个静态方法
 
        Object o = new Object(); 
        o.GetHashCode();//调用一个虚实例方法
        o.GetType();//调用一个非虚实例方法
    } 
}

编译上述代码,查看最后得到的IL代码,结果如下所示:

.method public hideby sigstatic void Main() cil managed{ 
   .entrypoint 
   //代码大小 26(0x1a) 
   .maxstack 1 
   .locals init(objectV_0) 
   IL_0000: call void System.Console::WriteLine() 
   IL_0005: newobj instance void System.Object::.ctor() 
   IL_000a: stloc.0 
   IL_000b: ldloc.0 
   IL_000c: callvirt instance int32 System.Object::GetHashCode() 
   IL_0011: pop 
   IL_0012: ldloc.0 
   IL_0013: callvirt instance class System.Type System.Object::GetType() 
   IL_0018: pop 
   IL_0019: ret 
}//Program::Main方法结束

首先注意,C#编译器使用IL指令call调用Console的WriteLine方法,这与期望是相符的,因为WriteLine方法是静态方法。接着注意,C#编译器使用IL指令callvirt调用GetHashCode方法,这也与期望相符,因为GetHashCode方法是虚方法。最后注意,C#编译器同样使用IL指令callvirt调用GetType方法,这令人惊讶,因为GetType方法不是虚方法。但是,该调用可以正常调用,这是因为JIT编译上述代码时,CLR知道GetType方法不是虚方法,因此,JIT编译的代码自然将以非虚的方式调用GetType方法。

当然,问题在于,为什么C#编译器只生成call指令,而不是其他指令呢?答案就是因为C#的工作组决定JIT编译器应生成验证所使用的对象的代码,以确定调用不为null。这意味着对非虚的实例方法的调用要比其应有的运行速度稍慢一点,同样这也意味着下面的C#代码将抛出一个NullReferenceException异常。在其他一些编程语言中,下述代码将正确运行。

using System; 
 
public sealed class Program{ 
    public Int32 GetFive(){return5;} 
    public static void Main(){ 
        Program p = null; 
        Int32 x = p.GetFive();//在C#中,会抛出一个NullReferenceException异常
    } 
}

从理论上讲,上述代码运行良好。的确,变量p为null,但是当调用非虚方法(如GetFive)时,CLR仅需要了解p的数据类型(p的数据类型为Program)。如果确实调用了GetFive方法,this参数的值将为null。因为GetFive方法中没有使用这个参数,因此不会抛出NullReferenceException异常。但是,因为C#编译器生成了一个callvirt指令,而不是生成了一个call指令,所以上述代码将抛出一个NullReferenceException异常并结束。

 重要提示 如果将某个方法定义为非虚拟的,那么,将来永远不能将方法改为虚拟的。这是因为某些编译器会使用call指令而不是callvirt指令来调用非虚拟的方法。如果将方法从非虚拟的改为虚拟的,而且没有重新编译所涉及的代码,那么虚方法将被非虚拟地调用,致使应用程序产生无法预测的行为。如果所涉及的代码是用C#编写的,这就不是一个问题了,因为C#使用callvirt指令调用所有的实例方法。但是,如果所涉及的代码使用了不是C#的其他编程语言,这将产生问题。

有时,编译器会使用call指令代替callvirt指令来调用虚方法。起初这可能会令人惊讶,但是下述代码将说明为什么有时需要这么做:

internal class SomeClass {
    //ToString是一个定义在基类Object中的虚方法
    public override String ToString() {
    //编译器使用IL指令'call'以非虚拟的方式调用object的ToString方法

    //如果编译器用'callvirt'指令取代'call'指令,那么该方法将递归地调用其本身,直至堆栈溢出
    return base.ToString();
    }
} 

调用虚方法base.ToString时,C#编译器生成一个call指令来确保非虚拟地调用基础类型中的ToString方法。需要这样做的原因在于:如果虚拟地调用ToString方法,那么调用将会递归执行,直至线程的堆栈溢出,这明显不是希望的结果。

编译器在调用值类型定义的方法时倾向于使用指令call,因为值类型是密封的。这意味着即使对于虚方法,也不存在多态,这将改善调用的性能,使调用速度更快。另外,值类型实例的本质保证了它永远不为null,因此永远不会抛出NullReferenceException异常。最后,如果虚拟地调用值类型的虚方法,那么,CLR为了在其内部引用方法表,需要引用值类型的类型对象,这需要对值类型进行装箱(boxing)。装箱给堆栈增加了更多的压力,强制进行更频繁的垃圾收集,使性能受到影响。

无论是否使用call指令和callvirt指令来调用实例方法或者虚方法,这些方法通常接收一个隐藏的this参数作为方法的第一个参数。this参数引用要进行操作的对象。

在设计类型的过程中,应尽量减小所定义的虚方法的数量。首先,调用虚方法的速度比调用非虚方法的速度要慢;其次,JIT编译器不能内联虚方法,这进一步影响了性能;第三,虚方法使组件的版本控制更脆弱,详见下节描述;第四,在定义基础类型时,通常需要提供一组有用的重载方法,如果希望这些方法是多态的,那么最好的办法就是将最复杂的方法虚拟化,而将所有有用的重载方法非虚拟化。附带提一下,遵循该原则同样会改善组件的版本控制能力,而不会影响派生类型的性能。下面给出示例:

public class Set {
    private Int32 m_length = 0;

    //这个有用的重载是非虚拟的
    public Int32 Find(Object value) {
        return Find(value, 0, m_length);
    }

    //这个有用的重载是非虚拟的
    public Int32 Find(Object value, Int32 startIndex) {
        return Find(value, 0, m_length);
    }
    //功能最丰富的方法是虚拟的,它可以被重写
    public virtual Int32 Find(Object value, Int32 startIndex, Int32 endIndex) {
        //重写的实际实现在此实现…
    }

    //其他方法在此实现
} 

【责任编辑:董书 TEL:(010)68476606】

回书目   上一节   下一节
专题
开源虚拟化技术Xen
C#实用基础教程
虚拟化技术还有点“虚”
虚拟机软件入门
服务器虚拟化
我也说两句

匿名发表

(如果看不清请点击图片进行更换)


中 国 最 大 的 网 络 技 术 网 站 ·
技 术 成 就 梦 想
订阅技术快讯
电子杂志下载
名称:SQL Server数据库管理精品黄皮书
简介:书中文章经过精挑细选,便于用户能根据自己的实际工作和学习,快速在本书寻找到相关资料。内容涵盖了SQL Server的安装与升级、语句查询、数据备份和恢复、自动化任务、数据同步、数据字典、安全和预防、性能和优化、集群等各方面应用信息,以及DBA管理人员在数据库管理工作中
名称:2007路由技术大全
简介:《2007路由技术大全》由51CTO.com网站特别策划制作,该书包括路由器技术、路由器产品、路由器配置、安全设置、路由器故障处理、路由器密码恢复,以及广大网友在实践使用中的心得经验和技巧文章,内容注重实用性,适用于初学者入门,也适合多年从业者提高,是一本实践和理论完
名称:网络安全精品应用黄皮书
简介:《2007精品网络安全黄皮书》包括了9个大类24个小类, 800余篇文章,内容包含了熊猫烧香病毒、DDOS攻击、ARP病等热点问题的介绍及解决方案。从病毒查杀、防范、系统、数据等各方面的安全设置到黑客技术的了解、防范,涉及到了安全应用的全部领域, 由浅至深内容全面。
世纪枭雄比尔盖茨的王者传奇
世纪枭雄比尔盖茨的王者传奇
虚拟化技术还有点“虚”
虚拟化技术还有点“虚”
ASP.NET开发教程
ASP.NET开发教程
· ASP.NET开发教程
· 专题:ASP.NET 2.0基础..
· LAMP技术精解
· 服务器节能与绿色IT
· ARP攻击防范与解决方案
· Linux 集群技术专题
· Windows集群服务应用
· CISSP认证成长之路
· SQL Server 2008/2005..
· SQL Server入门到精通
· 网络工程师职业规划与..
· 浏览器的战国时代
· 运营商封堵ADSL共享 中..
· 微软出价446亿美元收购..
· 技术人求职简历完备手册
· 开源虚拟化技术Xen
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
SOA 面向服务架构
SOA 面向服务架构
· SOA 面向服务架构
· SQL Server 2008/2005..
· Apache技术专题
· 三层交换技术专题
· SQL Server入门到精通
· Apache技术专题
· Windows集群服务应用
· 国际文档格式标准开战
· 路由器设置与口令恢复
· Linux 集群技术专题
· PHP开发应用手册
· SOA 面向服务架构
· 企业数据恢复指南
· 了解统一威胁管理(UTM)..
· 专题:AIX操作系统管理..
· 访问控制列表(ACL)介绍
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
SQL Server入门到精通
SQL Server入门到精通
· SQL Server入门到精通
· SQL Server 2008/2005..
· SOA 面向服务架构
· Apache技术专题
· 三层交换技术专题
· Apache技术专题
· 企业数据恢复指南
· Windows集群服务应用
· 路由器设置与口令恢复
· Linux 集群技术专题
· SOA 面向服务架构
· 了解统一威胁管理(UTM)..
· 反垃圾邮件技术应用
· 访问控制列表(ACL)介绍
· ASP.NET开发教程
· PHP开发应用手册