zoukankan      html  css  js  c++  java
  • 《CLR via C#》读书笔记 之 类型基础 明

    第四章 类型基础

    2013-02-27

    4.2 类型转换
    4.4 运行时相互关系
          例1 展示了在调用方法时,线程栈是如何处理局部变量和参数的

               托管堆的内存分配机制
          例2 展示了在调用方法时,托管堆是如何工作的

              JIT何时创建类型对象
              创建对象
              调用静态方法
              调用非虚实例方法
              调用的是虚实例方法

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

          小结

    参考

    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 Program
    12     {
    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]第十九回:对象创建始末(下) http://www.cnblogs.com/anytao/archive/2007/12/07/must_net_19.html

    【2】       [你必须知道的.NET]第十五回:继承本质论 http://www.cnblogs.com/anytao/archive/2007/09/10/must_net_15.html

  • 相关阅读:
    配置磁盘映射(在服务器和eclipse 中)
    服务器mysql授权连接用户
    validationEngine验证的使用
    Remove '@override' annotation解决办法
    js页面报错javax.servlet.jsp.PageContext cannot be resolved to a type解决
    如何正确使用log4j
    Log4j使用教程
    windows系统修改mysql端口的方法
    on条件与where条件的区别
    mybaits错误解决:There is no getter for property named 'id' in class 'java.lang.String'
  • 原文地址:https://www.cnblogs.com/Ming8006/p/2934699.html
Copyright © 2011-2022 走看看