对于方法的调用,很是令我头疼,什么静态方法,实例方法,实例虚方法,这里查了很多资料,总结如下:

这里声明,我也是菜鸟,这里只讨论方法的调用相关的技术,属于个人理解,如有错误,请指正

思路:

1 clr在加载类型的过程中方法表是怎么样构建的?

2 在程序调用方法时是怎样确定使用哪个类型的方法表的?

3 在程序调用方法时是怎样确定方法在方法表中的位置的(位于方法表的第几个方法)?

一 、方法在方法表中的排列顺序:

继承的实例虚方法、实例虚方法、构造函数、静态方法、实例方法

方法表排列原则:

1 在类的方法表的构造过程中:虚方法总是在子类的方法表中被复制的;实例方法,构造函数,静态方法等其他方法则在子类的方法表中不继承的

2 在类的方法表中:虚方法总是排在方法表的开头位置;继承的虚方法在最前面,新建的虚方法紧随其后(如图)

3 虚方法后边依次排列的是构造函数、静态方法、实例方法

为什么把“继承的实例虚方法”和“实例虚方法”放在方法表的开头位置?

在这种情况下每个虚方法在 相关的类的 方法表中 的位置都是不变的(无论是在其在创建方法的类中还是在派生类中):比如一个虚方法在类中的次序是第k个,那么他在其子类或父类(如果父类中有这个方法)中的位置都是第k个。

如果子类中新添加了虚方法,因为在新填的虚方法之前,已经把父类的方法表中的虚方法都复制到了子类的方法表最前面,所以父类中所有的方法在其子类中的位置序号都是不变的。

如果子类中新添加了除了虚方法之外的其他方法(实例方法,构造函数,静态方法等),这些方法也都是排在虚方法之后

以上两点就保证了虚方法无论是在其自身的类、父类、子类中其在方法表中的位置(位于方法表的第几个)都是不变的

结论:方法表中虚方法的排序,可以在类的层次结构中保持虚方法的层次结构,这是实现多态的基础,也就是为什么说继承是实现多态的基础了。

例子:

类的定义代码如下:

    class Program{static void Main(string[] args){Father son = new Son();son.DoWork();son.DoVirtualWork();son.DoVirtualAll();Son.DoStaticWork();Father aGrandson = new Grandson();aGrandson.DoWork();aGrandson.DoVirtualWork();aGrandson.DoVirtualAll();Console.ReadKey();}}public class Father{public void DoWork(){Console.WriteLine("Father.DoWork()");}public virtual void DoVirtualWork(){Console.WriteLine("Father.DoVirtualWork()");}public virtual void DoVirtualAll(){Console.WriteLine("Father.DoVirtualAll()");}}public class Son : Father{public static void DoStaticWork(){Console.WriteLine("Son.DoStaticWork()");}public new void DoWork(){Console.WriteLine("Son.DoWork()");}public new virtual void DoVirtualWork(){Console.WriteLine("Son.DoVirtualWork()");}public override void DoVirtualAll(){Console.WriteLine("Son.DoVirtualAll()");}}public class Grandson : Son{public override void DoVirtualWork(){Console.WriteLine("Grandson.DoVirtualWork()");}public override void DoVirtualAll(){Console.WriteLine("Grandson.DoVirtualAll()");}}public class GrandGrandson : Grandson{public new virtual void DoVirtualWork(){Console.WriteLine("GGson.DovirtualWork()");}public override void DoVirtualAll(){Console.WriteLine("GGson.DoVirtualAll()");}}

示例代码

 Entry       MethodDe    JIT Name
6751cd88 672360bc PreJIT System.Object.ToString()
67516a90 672360c4 PreJIT System.Object.Equals(System.Object)
67516660 672360e4 PreJIT System.Object.GetHashCode()
675967c0 672360f8 PreJIT System.Object.Finalize()
003201c8 001d3824    JIT MethodInvoke.Father.DoVirtualWork()
001dc035 001d382c   NONE MethodInvoke.Father.DoVirtualAll()
00320158 001d3834    JIT MethodInvoke.Father..ctor()
00320190 001d3818    JIT MethodInvoke.Father.DoWork()

Father类的方法表

   Entry    MethodDe    JIT Name
6751cd88 672360bc PreJIT System.Object.ToString()
67516a90 672360c4 PreJIT System.Object.Equals(System.Object)
67516660 672360e4 PreJIT System.Object.GetHashCode()
675967c0 672360f8 PreJIT System.Object.Finalize()////前四个方法是继承自Object类的方法
003201c8 001d3824    JIT MethodInvoke.Father.DoVirtualWork()
00320200 001d38b8    JIT MethodInvoke.Son.DoVirtualAll()//这也是继承的Father类的虚方法,只不过在Son类中重写的方法覆盖了//这两个类是继承自Father类的方法
001dc059 001d38b0   NONE MethodInvoke.Son.DoVirtualWork()//这个是Son类中新建的方法
00320120 001d38c0    JIT MethodInvoke.Son..ctor()//Son类的构造函数
00320238 001d3898    JIT MethodInvoke.Son.DoStaticWork()//Son类的静态方法
001dc055 001d38a4   NONE MethodInvoke.Son.DoWork()//Son类的实例方法

Son类的方法表

   Entry     MethodDe    JIT Name
6751cd88 672360bc PreJIT System.Object.ToString()
67516a90 672360c4 PreJIT System.Object.Equals(System.Object)
67516660 672360e4 PreJIT System.Object.GetHashCode()
675967c0 672360f8 PreJIT System.Object.Finalize()
003201c8 001d3824    JIT MethodInvoke.Father.DoVirtualWork()
003202a8 001d3930    JIT MethodInvoke.Grandson.DoVirtualAll()
001dc079 001d3928   NONE MethodInvoke.Grandson.DoVirtualWork()
00320270 001d3938    JIT MethodInvoke.Grandson..ctor()

Grandson类的方法表

二、方法表中方法的确定:

1 最简单的是非虚的方法

    这个只有一种情况,方法在哪个类中,该方法就是在那个类中定义的。因为这些非虚的方法,并不会在其子类中复制其方法。Son类的方法表中最后两个方法

MethodInvoke.Son.DoStaticWork()
MethodInvoke.Son.DoWork()这两个类是Son类中定义的,因此也只有Son类的方法表中才会有这两个方法就验证了这个说法。

2 对于虚方法

方法表中的方法有可能有三种来源

a 来自其所在类新建的虚方法,这种情况会在方法表中适当的位置新加一个方法表槽(使用new virtual 和virtual关键字)

例如:public new virtual void DoVirtualWork()和public virtual void NewMethod()

b 通过继承父类并且在类中重新定义的虚方法,这种情况在把父类的方法复制到该类的方法表中后,使用重新定义的方法将其覆盖掉,不会新建方法表槽(使用override关键字)

 例如:public override void DoVirtualWork()

c 通过继承父类的虚方法,这种情况不用使用任何关键字,他只是把父类的方法复制到该类的方法表中。

b c两种其实都继承了父类中该方法在方法表中的位置,而a则是在该类的方法表中新添加了位置(新见了方法表槽)

三、方法的调用:

要讲明白方法的调用,先要解释几个名词(自己理解的名词)

引用变量:是指在声明时的那个变量,如object a;这里的a就是引用变量

对象、实例:在实例化中建立的那个对象,如new object(),会创建一个object对象(并且返回一个对象的引用)

从C#到IL:

首先看看从C#语言到IL语言,C#编译器是怎么翻译的

在调用方法的时候,C#编译器关注的是引用变量的类型,它并不会关心实例类型是什么。C#编译器会从引用变量的类型开始向其父类逐层查找:

a 对于实例虚方法,直到查找到virtual关键字的时候,就会翻译为该方法的调用。如:

  对于上面代码中的类型,如果我有代码Father gd=new Grandson();gd.DoVirtualWork(),那么在IL中会翻译成callvirt/call Father::DoVirtualWork()

  如果有代码Grandson gd=new Grandson();gd.DoVirtualWork(),那么在IL中就会翻译成callvirt/call Son::DoVirtualWork()

这里通常情况用的是callvirt,但在有些情况是会用call的:

-比如一个密封类引用的虚方法就可以用call,因为可以确定没有派生类,不会调用派生类中的方法了,使用call可以避免进行类型检查,提高性能

-值类型调用虚方法时也会用call,值类型首先是一个密封类型,其次call调用可以阻止值类型被执行装箱

-在类型定义中,调用基类的虚方法时,采用call可以避免callvirt递归调用本身引起的堆栈溢出,如

class call_callvirt
{public override bool Equals(object obj){return base.Equals(obj);}
}    

调用基类虚方法

b 对于非虚方法,直到查找到第一个含该方法的定义类时,就会调用该方法。如:

  对于上面代码中的类型,如果我有代码Father gd=new Grandson();gd.DoWork(),那么在IL中会翻译成call/callvirt Father::DoWork()

  如果有代码Grandson gd=new Grandson();gd.DoWork(),那么在IL中就会翻译成call/callvirt Son::DoVirtualWork()

  这里通常用的用的是call,但也有使用callvirt的情况:

-常见的在引用类型中使用callvirt,因为引用变量为null时会抛出异常NullReferenceException,而call则不会抛出任何异常,为类型安全起见在C#中会调用callvirt来完成非虚类型的调用

总的来说,在C#中对方法的调用在IL中基本都翻译成了call/callvirt(还有calli在C#中我很少见倒)指令调用方法,虽然call和callvirt用法比较乱,但是骨子里还是有区别的:

call用来调用静态类型、声明类型的方法,而callvirt调用动态类型、实际(实例)类型的方法

从IL到localcode

这里首先要讲的就是IL中call和callvirt的区别了:

call 直接调用函数(由上一部分知道,这里调用的函数是由引用变量的类型决定的);

执行静态调度:在编译期间就可以确定其执行的操作(编译的时候就可以确定使用哪个类型的方法表

callvirt会检查引用变量所指向的实例的类型(包括是否是null引用),并且在实例的类型的方法表中调用对应位置的方法(这个命令实际上就是说知道了方法在方法表中的位置,由实例的类型决定使用哪个方法表中对应位置的方法)

执行动态调度:在运行时才能确定执行的操作(需要运行时判断引用变量所指向的实例的类型,进而确定该实例类型的方法表为要使用的方法表

另外的方法调用:

基于反射技术的动态调度机制,基本原理是在运行时,查找方法表的信息来实施调用的方式。常见的方式有:MethodInfo.Invoke()方式和动态方法委托(Dynamic Method Delegate)方式。

四、贴出去的代码的结果如下图:

回答问题:

假定有ABC三个类型,且A<--B<--C

1 clr在加载类型的过程中方法表是怎么样构建的?

  这里只关心方法表,类的其他部分忽略:clr在实例化类型的实例的时候,需要在之前加载好类型:

加载object类(如果还未加载,下同);

然后加载类A,在这个过程中先将object类的虚方法复制在A类的方法表中,然后依次排列A类的虚方法,构造函数,静态方法,实例方法;

然后加载类B,在这个过程中先将A类的虚方法复制在A类的方法表中,然后依次排列B类的虚方法,构造函数,静态方法,实例方法;

然后加载类C,在这个过程中先将B类的虚方法复制在C类的方法表中,然后依次排列C类的虚方法,构造函数,静态方法,实例方法;

.....依次类推,这就构成了各个类自己的方法表,方法表中包括了继承的虚方法、虚方法、构造函数、静态方法、实例方法

2 在程序调用方法时是怎样确定使用哪个类型的方法表的?

对于非虚方法:“引用变量”是哪个类型,就使用哪个类型的方法表

对于虚方法:“引用变量指向的对象类型” 是什么类型,就使用哪个类型的方法表

3 在程序调用方法时是怎样确定方法在方法表中的位置的(位于方法表的第几个方法)?

即“引用变量类型”的方法表中该方法的位置(由于虚方法表的特点决定了  虚方法的位置  在  类层次结构中的各个类的方法表  中  是相同的,也即虚方法的位置被其子类继承了下来)

后记:这篇文章查了网上很多牛人的博客,还有参考了《你必须知道的.net》王涛 等资料,感谢这些牛人的辛勤劳动成果,自己写了才知道来之不易啊。由于笔者首次写,有很多不足之处,希望指正

转载于:https://www.cnblogs.com/OneRipple/p/7093876.html

c#类的方法表的建立和方法的调用相关推荐

  1. java方法调用之动态调用多态(重写override)的实现原理——方法表

    转自:http://blog.csdn.net/fan2012huan/article/details/51007517 上两篇篇博文讨论了java的重载(overload)与重写(override) ...

  2. JVM虚拟机-Class文件之方法表集合

    一.概述 方法表集合与属性表集合的结构类似,是对方法的修饰符.返回类型.方法名.参数个数.参数类型.方法体的描述集合. 方法表集合的结构是一个类似于数组的结构,JVM在对java文件进行编译时,会将类 ...

  3. 【Java 虚拟机原理】Class 字节码二进制文件分析 五 ( 方法计数器 | 方法表 | 访问标志 | 方法名称索引 | 方法返回值类型 | 方法属性数量 | 方法属性表 )

    文章目录 前言 一.方法表结构 二.方法计数器 三.方法表数据解析 ( init 构造方法 ) 1.方法访问标志 2.方法名称索引 3.方法返回类型 4.方法属性数量 前言 上一篇博客 [Java 虚 ...

  4. java tostring方法_Java虚拟机如执行方法调用的(二)?

    虚方法调用 Java里所有非私有实例方法调用都会被编译成invokevirtual指令. 接口方法调用都会被编译成invokeinterface指令.这两种指令都属于Java虚方法的调用. 在大多数情 ...

  5. c#自带压缩类实现数据库表导出到CSV压缩文件的方法

    原文:c#自带压缩类实现数据库表导出到CSV压缩文件的方法 在导出大量CSV数据的时候,常常体积较大,采用C#自带的压缩类,可以方便的实现该功能,并且压缩比例很高,该方法在我的开源工具DataPie中 ...

  6. c++ 每个类都有一张虚方法表

    #include <iostream> using namespace std; /* 每个类都有一张虚方法表,当基类为虚方法,而派生类重载了虚方法, * 则虚方法表中的基类方法被派生类替 ...

  7. java实体类生成mysql表_springboot+mybatis通过实体类自动生成数据库表的方法

    前言 本章介绍使用mybatis结合mysql数据库自动根据实体类生成相关的数据库表. 首先引入相关的pom包我这里使用的是springboot2.1.8.RELEASE的版本 org.mybatis ...

  8. hibernate继承关系映射方法(三)--每个具体类一张表TPC

    TPC:所谓是"每个具体类一张表(table per concrete class)"的意思是:使继承体系中每一个子类都对应数据库中的一张表.每一个子类对应的数据库表都包含了父类的 ...

  9. 虚方法表与动态分派机制详解

    在上两篇中分别对方法重载[https://www.cnblogs.com/webor2006/p/9723289.html]和方法重写[https://www.cnblogs.com/webor200 ...

  10. 计算机绘制轴类零件图,轴类零件工序图自动绘制的方法和系统研究

    摘要: 计算机辅助工艺设计(Computer Aided Process Planning)是连接CAD/CAM的桥梁,是实现CIMS的关键环节,鉴于CAPP在制造业中的重要地位,一直成为国内外学者关 ...

最新文章

  1. BFS:走出迷宫并输出最小步数
  2. 60岁代码匠的几篇小作文,解决了大多数程序的迷茫(下)
  3. *44.程序的链接方式
  4. oracle jdk_两个Oracle JDK的故事
  5. 2 计算机网络性能指标
  6. 2008年度一个下岗程序员的真实经历
  7. T1155 金明的预算方案 codevs
  8. redis 设置不过期_面试时 Redis 内存淘汰总被问,但是总答不好,怎么解决?
  9. Java sychronized关键字总结(二)
  10. 实对称矩阵的特征值求法_“绝境之下”,如何求解矩阵的特征值?
  11. android post请求时报415错误,post请求传递JSON数据类型(415错误解决)
  12. win10硬盘锁怎么解除_win10系统下bitlocker给硬盘加密后怎么解锁
  13. 软件构造设计模式III(转载整合)
  14. 网站的页面该如何去设计与布局
  15. 看大牌厂商PK——2010 ARM研讨会见闻
  16. Markdown内嵌图片的解决方法
  17. 大厂面试干货:面试官最喜欢pick什么样的候选人
  18. TOM企业邮箱安全卫士告诉你,如何告别邮箱被盗
  19. html点击div等元素隐藏光标
  20. 分析总结常见的几种移动机器人底盘类型及其运动学

热门文章

  1. (译)如何使用NSCoding和NSFileManager来保存你的应用程序数据
  2. 驗證類javascript
  3. 为类型库(Type Library)生成帮助文件
  4. 序列化和反序列化(八)——Externalizable接口
  5. Security+Oauth2权限认证(案例 源码)
  6. JS获取url参数,主域名等方法
  7. JavaWeb知识点
  8. 专线服务器安全维护,服务器系统安全维护
  9. python 新建一列_创建一个空数据框并在python中填充创建的列
  10. yorc.json_调用腾讯ORC接口识别图片文字