c#类的方法表的建立和方法的调用

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

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

思路:

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》王涛 等资料,感谢这些牛人的辛勤劳动成果,自己写了才知道来之不易啊。由于笔者首次写,有很多不足之处,希望指正

时间: 2024-11-01 01:55:33

c#类的方法表的建立和方法的调用的相关文章

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

原文:c#自带压缩类实现数据库表导出到CSV压缩文件的方法 在导出大量CSV数据的时候,常常体积较大,采用C#自带的压缩类,可以方便的实现该功能,并且压缩比例很高,该方法在我的开源工具DataPie中已经经过实践检验.我的上一篇博客<功能齐全.效率一流的免费开源数据库导入导出工具(c#开发,支持SQL server.SQLite.ACCESS三种数据库),每月借此处理数据5G以上>中有该工具的完整源码,有需要的同学可以看看. 在.net 4.5中,可以轻松创建zip文件 ,首先需要引入 Sys

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

TPC:所谓是"每个具体类一张表(table per concrete class)"的意思是:使继承体系中每一个子类都对应数据库中的一张表.每一个子类对应的数据库表都包含了父类的信息,并且包含了自己独有的属性.每个子类对应一张表,而且这个表的信息是完备的,即包含了所有从父类继承下来的属性映射的字段.这种策略是使用<union-subclass>标签来定义子类的. 注意:三个类+一个父类映射文件+两张表 student表 worker表 测试工程: Person.java

Mybatis中实体类属性与数据库列表间映射方法介绍

           这篇文章主要介绍了Mybatis中实体类属性与数据列表间映射方法介绍,一共四种方法方法,供大家参考.         Mybatis不像Hibernate中那么自动化,通过@Column注解或者直接使用实体类的属性名作为数据列名,而是需要自己指定实体类属性和数据表中列名之间的映射关系,这一点让用惯了Hibernate的人很不习惯,所幸经过探索找到了建立映射关系的几种办法,其中总也有比较简单的. 首先定义一个实体类User,如下: public class User { pr

实体类和数据表的映射异常(XXX is not mapping[ ])

在使用SSH框架开发过程,使用hibernate框架提供的工具类实现与数据库数据交互,在执行cmd操作时,如果出现以下异常: org.hibernate.hql.ast.QuerySyntaxException: xxx is not mapped [from xxx] 或者 nested exception is org.hibernate.hql.internal.ast.QuerySyntaxException 详细异常信息   21:08:31,951 ERROR DefaultDisp

领接表的建立和它的DFS, BFS;;;

//图的建立的实现->邻结矩阵和邻结表两种表示方法 #include <cstdio> #include <cstdlib> //#define _OJ_ int visit[100]; typedef struct Lnode { int data; //邻结点的位置下标 // int weight; struct Lnode *next; //表由多排的链表组成 } Lnode, *Linklist; typedef struct Fnode { int elem; //

10天学会phpWeChat——第八天:Form类,丰富表单提交的字段类型

通过前面七讲的系列教程,我们完成了一个包含后台并自适应PC+h5移动端的文章管理模块. 在实际的生产环境中,文章投稿.商品上传等操作并不会简单局限于一个text和textarea组成的表单.在实际中,我们可能会用到web富文本编辑器(如ueditor.markdown).图片上传.多图上传.附件上传.地图标注等更加丰富的表单类型. 今天,我们开始<10天学会phpWeChat>的第八讲:Form类,丰富表单提交的字段类型. 一.什么是Form类? Form类是phpWeChat封装好的一个类,

怎样使用CPropertyPage类和CPropertySheet类创建属性表

CPropertyPage类是属性页类,相当于每一个类就是一个对话框.它继承自CDialog类. CPropertySheet类是属性表类,它有多个属性页.它继承自CWnd类 创建属性表步骤如下: 这里简单的以只有两个属性页的属性表为例子. 1.创建一个主对话框. 如图: 2.在资源中创建一个菜单 如图: 3.给菜单添加事件处理程序 如图: 4.在主对话框属性中添加菜单 如图: 5.添加两个对话框作为属性页 如图: 需要注意的是,对话框外框最好设置为thin(MSDN说的),不过我测试的时候感觉

mysql分表的三种方法

mysql分表的3种方法 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. 根据个人经验,mysql执行一个sql的过程如下:1,接收到sql;2,把sql放到排队队列中 ;3,执行sql;4,返回执行结果.在这个执行过程中最花时间在什么地方呢?第一,是排队等待的时间,第二,sql的执行时间.其实这二个是一回事,等待的同时,肯定有sql在执行.所以我们要缩短sql的执行

JVM-class文件完全解析-方法表集合

方法表集合 前面的魔数,次版本号,主板本号,常量池入口,常量池,访问标志,类索引,父类索引,接口索引集合,字段表集合,那么再接下来就是方法表了.   方法表的构造如同字段表一样,依次包括了访问标志(access_flags),名称索引(name_index),描述符索引(descriptor_index),属性表集合(attributes)几项. 方法表结构: 类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2