博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《CLR via C#》读书笔记 之 类型基础
阅读量:6863 次
发布时间:2019-06-26

本文共 5734 字,大约阅读时间需要 19 分钟。

第四章 类型基础

2013-02-27

     

          

      

         

         
         
         
         

      

     

4.2 类型转换


CLR最重要的特性之一就是类型安全性。在运行时,CLR总能知道一个对象时什么类型。由于GetType是非虚方法,所以一个类型不可能伪装成另一个类型,通过这个方法,可以知道对象的确切类型。

CLR允许将一个对象转换为它的(实际)类型或它的任何基类型。

使用C#is和as操作符

is 检查一个对象是否兼容于(实际类型或基类型)指定类型,并返回一个布尔值。

as 检查一个对象是否能强制转换成指定类型,若不可以,返回null

4.4 运行时相互关系


 

本节将揭示类型、对象、线程栈和托管堆在运行时的相互关系。此外,还将解释调用静态方法、实例方法和虚方法的区别。

例1 展示了在调用方法时,线程栈是如何处理局部变量和参数的

图1展示了已加载了CLR的一个进程。在这个进程中,可能有多个线程。一个线程创建时,会分配到1MB大小的栈。这个栈空间用于向方法传递实参,也用于方法内部的局部变量。在图中,线程已执行了一些代码,准备调用M1方法。

 

图1一个线程的栈,准备调用M1方法

 

图2 在线程栈上分配M1方法中的局部变量

 

图3 M1调用M2时,将实参和返回地址压入栈中

 

图4 在线程栈中分配M2的局部变量

在线程执行M2内部代码,最终抵达return语句,造成CPU指针被设置成栈中的返回地址,而且M2的栈帧(代表当前线程的调用栈的一个方法调用)会展开(unwind),使线程栈返回到M1调用M2之前的状态,如图2。同理M1调用完后,线程栈会返回到如图1的状态。

托管堆的内存分配机制【1

引用类型的实例分配于托管堆上,而线程栈却是对象生命周期开始的地方。对32位处理器来说,应用程序完成进程初始化后,CLR将在进程的可用地址空间上分配一块保留的地址空间,它是进程(每个进程可使用4GB)中可用地址空间上的一块内存区域,但并不对应于任何物理内存,这块地址空间即是托管堆。

托管堆又根据存储信息的不同划分为多个区域,其中最重要的是垃圾回收堆(GC Heap)和加载堆(Loader Heap),GC Heap用于存储对象实例,受GC管理;Loader Heap又分为High-Frequency Heap、Low-Frequency Heap和Stub Heap,不同的堆上又存储不同的信息。Loader Heap最重要的信息就是元数据相关的信息,也就是Type对象,每个Type在Loader Heap上体现为一个Method Table(方法表),而Method Table中则记录了存储的元数据信息,例如基类型、静态字段、实现的接口、所有的方法等等(此句与本书下面例2中的类型对象有矛盾,不过本书中的类型对象更易理解)。Loader Heap不受GC控制,其生命周期为从创建到AppDomain卸载。

例2 展示了在调用方法时,托管堆是如何工作的

源代码:

1 internal class Employee 2 { 3      public Int32 GetYearsEmploye() { return -1; } 4      public virtual String GenProcessReport() { return string.Empty; } 5      public static Employee LockUp(String name) { return null; } 6 } 7 internal class Manager : Employee 8 { 9      public override string GenProcessReport() { return base.GenProcessReport(); }10 }

注意区分堆中的类型对象对象

 

图5 CLR已加载到进程中,它的堆已初始化,一个线程的栈已创建,现在马山要调用M3

 

图6 Employee和Manager类型对象会在M3被调用创建

JIT何时创建类型对象

当JIT编译器将M3的IL代码转换成本地CPU指令时,会注意到M3内部引用的所有类型:Employee,Int32,Manager以及String(”Joe”)。这个时候,CLR要确保定义了这些类型的所有程序集都已加载。然后,利用程序集的元数据,CLR提取与这些类型有关的信息,并创建一些数据结构来表示类型本身。在图6中,为了一目了然,我们在堆中只显示Emplyee和Manager,Int32和String类型对象很可能在调用M3之前就被创建了。

类型对象包括:

(1)类型对象指针(type object pointer)

(2)同步索引块(sync block index)

(3)静态数据字段

(4)方法表

 

图7 在线程栈上分配M3的局部变量

 

图8 分配并初始化一个Manager对象

CLR在堆中创建对象的步骤:

(1)       初始化类型对象指针,使之指向对应类型对象

(2)       初始化同步索引块

(3)       初始化实例字段(实例字段包括本身及其基类的实例字段)

(4)       调用类型的构造器(它本质上是可能修改某些实例数据字段的一个方法)。New操作法会返回对象内存地址,该地址保存在栈中的变量e中

 

图9 Employee的静态方法Lookup为Joe分配并初始化一个Manager对象

调用静态方法的步骤(为何如此复杂,参考8.6扩展方法):

(1)       CLR会定位于定义静态方法的对应的类型对象。

(2)       JIT编译器在类型对象的方法表中查找与被调用的方法对应的记录项,对方法进行JIT 编译(如有还没编译的话),在调用之。

假定Employee通过查询数据库查找名家“Joe”的employee,发现Joe是一个经理,所以在内部,Lookup方法在堆上构造了一个新的Manger对象。

注意,e不再引用第一个Manager对象。而且这个对象因没有变量引用,将会变成垃圾回收的主要目标。

 

图10 Employee的非虚实例方法GetYearsEmployed在调用后返回5

 

调用非虚实例方法的步骤:

(1)       JIT编译器会找到与“发出调用的那个变量e的类型(Employee)”对应的类型对象(Employee类型对象)。

(2)       如果Emplyee类中没有定义正在调用的方法,JIT编译器会回溯类层次结构图(一直回溯到Object),查找调用的方法。之所以可以回溯,是因为每个类型对象都有一个字段引用了它的基本类型,这个信息图中没有显示。

 

图11 调用Employee的虚实例方法GenProgressReport,最终执行的是Manager重写的这个方法

调用的是虚实例方法的步骤:

(1)       JIT要在方法中生成额外的代码,方法每次调用时,都会执行这些代码。

(2)       这些代码首先检查发出调用的变量,然后找到它所指的对象。在本例中e指向的是Manager对象。

(3)       然后找到对象指向的类型对象。在本例中Manager对象指向的是Manager类型对象。

(4)       若该类型对象方法表内的对应方法是非虚实例方法,调用之;若没找到对应的非虚实例方法,JIT编译器会回像调用非虚实例方法第二步一样回溯层次结构,直到找到匹配的方法。

注意:若Employee发现的Joe是个Employee,而不是Manager,Lookup会在堆内创建一个Employee对象,他的类型对象指针指向Employee类型对象。这样一来,最终执行的就是Eomployee的GenProgressReport实现,而不是Manager的GenProgressReport实现。

 

Emplyee和Manager类型对象都包含“类型对象指针“成员。这是由于类型对象本质上也是对象。CLR创建类型对象,必须初始化这些成员。那么如何初始化这些成员?

CLR开始在一个进程中运行时,会立即为MSCorLib.dll中定义的System.Type类型定义一个特殊的类型对象。Employee和Manager类型对象是该类型的“实例”。

当然System.Type类型对象本身也是一个对象,它比较特殊,她的“类型对象指针”指向它自己。

另外,System.Object的GetType方法返回的是对象所指向的类型对象。

class Base    {        public string name = "Base";        public static void StaticMethod()        {            Console.WriteLine("Base static method.");        }        public  void NonStaticMethod()        {            Console.WriteLine("Base non static method.");        }        public virtual void VirtualMethod()        {            Console.WriteLine("Base virtual method.");        }    }    class Derived : Base    {        public new string name = "Derived";        public new void NonStaticMethod()        {            Console.WriteLine("Derived non static method.");        }        public override void VirtualMethod()        {            Console.WriteLine("Derived virtual method.");        }    }
View Code

 

static void Main(string[] args)        {            Base.StaticMethod();      //Base static method.            Derived.StaticMethod();   //Base static method.            Base b=new Base();            Derived d=new Derived();            Base c = new Derived();            b.NonStaticMethod();      //Base non static method.            d.NonStaticMethod();      //Derived non static method.            c.NonStaticMethod();      //Base non static method.            b.VirtualMethod();        //Base virtual method.            d.VirtualMethod();        //Derived virtual method.            c.VirtualMethod();        //Derived virtual method.            Console.WriteLine(b.name);//Base            Console.WriteLine(d.name);//Derived            Console.WriteLine(c.name);//Base            Console.Read();        }
View Code

 

只有多态方法,没有多态实例字段【2】

从上述调用虚方法的得知,多态是如何实现的,即使变量e的类型是基类Employee,但只要它指向的对象是派生类Manager,且有覆盖虚方法的实例方法实现,就会调用Manager中的方法。

但实例字段并不会覆盖,见如下代码:

1 class Employee 2     { 3         public string name = "Employee"; 4     }  5  6     class Manager : Employee 7     { 8         public string name = "Manger"; 9     }10 11     class Program12     {13         static void Main(string[] args)14         {15             Employee e = new Manager();16             Console.WriteLine(e.name);   //显示:Employee     17         }18     }

上述代码,Employee e = new Manager(); 具体过程参见

 

小结:

  • 在使用变量调用方法或字段时,只有调用的是虚实例方法,是根据变量指向的对象,再找指向对象指向的类型对象中找相应的方法(多态方法是这样实现的);
  • 调用非虚实例方法或字段时,是直接根据变量的对象类型,找相应的方法或字段。

参考

【1】       [你必须知道的.NET]第十九回:对象创建始末(下) 

【2】       [你必须知道的.NET]第十五回:继承本质论

转载于:https://www.cnblogs.com/Ming8006/archive/2013/02/27/2934699.html

你可能感兴趣的文章
Git服务器安装详解及安装遇到问题解决方案
查看>>
asp.net mvc FluentValidation客户端验证失效
查看>>
【转载】VBA:调用文件夹对话框的几种方法
查看>>
【ocp-12c】最新Oracle OCP-071考试题库(38题)
查看>>
我为公司做的总体架构,欢迎提建议
查看>>
Apache的prefork模式和worker模式(转)
查看>>
android用于打开各种文件的intent
查看>>
PHP截取字符串
查看>>
php基础:变量,运算符总结
查看>>
函数和对象方法的区别
查看>>
程序猿装逼从注释开始
查看>>
寄存器AX
查看>>
javascript之复习(css属性值的计算)
查看>>
dijkstra算法与优先队列
查看>>
Spring Data JPA
查看>>
LeetCode - Count Primes
查看>>
Zabbix和MPM监控MySQL
查看>>
求三角形的周长类的取值范围
查看>>
easyUI的简单之处
查看>>
蓝牙协议学习---BLE地址类型
查看>>