基础类型

所有的类型都从 System.Object 派生

1、类型System.Object

运行时要求每一个类型都是从System.Object派生,如果没有显示的写明继承关系,最后都会默认的从System.Object来派生。System.Object提供了四个公用方法和两个收保护方法:

公共方法方法说明
Equals 比较两个对象的值是否相等,相等返回true
GetHashCode 返回对象的哈希码,如果想让对象在字典中做键使用,应当重写此方法提供一个分布均匀的哈希码,此方法本应该设计在接口中
ToString 默认返回类型的完整名称:this.GetType().F ullName()。VS调试器会自动调用改函数来显示对象的字符串表示。理论上此函数应该察觉到与调用线程的CutureInfo并采取相关行动
GetType 返回从Type派生的一个类型实例,是非虚方法。目的是为了防止类型重写改方法,隐瞒其类型,从而破坏其安全性
MemberwishClone 创建一个新实例,并且设置新的实例对象字段和当前对象相同的值,然后返回其引用
Finalize 垃圾回收之前执行,如果需要处理某些资源可以重写此方法

2、new 操作符需要干的事情

- 计算类型以及其基类(直到system.object)的实例字段需要的字节数,以及堆上额外成员需要的字节数。额外成员包括 类型对象指针 和同步索引块 - 从托管堆中分配自己数从而分配对象内存 - 初始化 类型对象指针 和同步索引块 成员的值 - 调用构造函数。每一个构造函数都负责初始化该类型定义的实例字段 - 返回一个引用

类型转换

3、类型安全

    CLR 最重要的一个特性就是类型安全。以为不管是什么变量,调用一下GetType就可以知道他的具体类型,此方法是非虚方法,不可以进行伪装和重写。而同时C#也规定,不要求任何特殊语法,就可将对象转换成对应的任何基类,因为面相基类的转换默认为是类型安全的。类型伪装是很多安全问题的根源,也会破坏程序的健壮性和稳定性。

4、is 和 as 运算符

    is 运算符专门用来检查对象是否可以兼容的转换为另外的一个类型的对象,永远不会抛出错误,只是会返回true或者false.

  if(o is Employee)
    {
        Employee e = (Employee)o;
    }

 

    上面的代码中,做了两次类型检查,增强了安全性,但无疑对性能造成一定的浪费。普通的强制类型转换过程:CLR首先必须判断变量(o)引用的对象的实际类型,然后CLR 类型便利继承的层次结构,用每一个基础类型去核对制定的类型(Employee)。

    as 运算符就是为了简化上面的常用编程模式而设计的,as运算符只是检查一次对象,如果对象位null就返回位null,然后就进行转换,如果转换不了就返回位null,而永远不会抛出错误。如下代码,if中间只是判断一个是否位null,相比速度会快很多

 

Employee e = o as Employee;
if(e != null)
{
    
}

 

命名空间和程序集

    命名空间只是对相关类型进行逻辑分组。对于编译器而言,命名空间的作用就是为了类型名称架上点分隔符让名称变得更加长,也更加具有唯一性。

    命名空间只是针对编译器,而CLR对命名空间是无概念的,在访问类型的时候,CLR需要知道类型的完整名称以及该类型定义在哪一个程序集里面,这样“运行时”才能正确的找到和加载正确的程序级,并且对其进行操作

    命名空间,和程序级之间无直接关系,一个命名空间的代码可以出现在多个程序级中,一个程序级也可以有多个命名空间。可以参考,System以及Linq相关程序级和命名空间

    using运算符,只是为了方便开发人员,少写一些类的全称部分,同样只是针对编译器有用,CLR并不认识这个语法。另外一个作用就是允许给类型和命名空间创建别名,如下:

using WintellectWidget = Wintellect.Widget 
public sealed class Program 
{ 
    public static void Main() 
    {
     WintellectWidget = new WintellectWidget(); 
    }
} 

 

    如果还是有在不同程序级中,同名的类型,此种情况可以通过外部程序级来解决(extern alias)

运行时的相互关系

    假定有如下两个类型的定义

  internal class Employee {
      public int GetYearsEmployed(){} 
      public virtual string GetProgressReport(){} 
    public static Employee Lookup(string name){}
  } 
  internal sealed class Manager:Employee 
  { 
      public overide String GetProgressReport(){}
  }

 

    如下图,程序以及执行过一段时间,现在即将调用M3方法。JIT将M3的IL代码编译成CPU的指令时,会注意到所有的类型,确保以及加载了具体对应的程序集。利用程序集的元数据,CLR提取与之相关的信息,创建数据结构来表示类型本身

    如图中,Employee和Manager类型对象都包含两个成员:类型对象指针和同步索引块。而在定义类型的时候,可以在类型内部定义静态数据字段,俄日这些静态数据字段提供支援的字节在类型对象自身中分配,每一个类型对象都包含一个方法表,在方法表中国,类型定义的每一个方法都有对应的记录项

 

    然后,M3执行代码构造一个Manager对象,造成在托管堆上创建Manager类型的一个实例对象。此对象也有类型对象指针和同步索引块,同时还包含必要的字节来容纳Manager类型定义的所有实例数据字段,以及容纳有Manager的任何基类定义的所有实例字段。

    任何时候在堆上创建对象时,CLR会自动初始化内部的"类型对象指针”成员来引用和对象对应的类型对象(也就是Manager类型对象),在执行构造函数之前还会先初始化同步索引块,并且将对象的实例字段设置为null或者0.然后通过new操作符返回地址

    M3的下一行代码是调用Employee的静态方法 Lookup 得到一个Manager对象。调用静态方法的时候,CLR会首先定位到静态方法的类型对应的类型对象,然后,JIT编译器在类型对象方法表中查找与被调用方法的对应的记录项,对方法进行JIT编译,然后在调用编译好的代码。

    M3接下来调用非虚实例方法,GetYearsEmployed。调用虚方法时,JIT编译器会找到发出调用的那个变量(e)的类型(Employee)对应的类型对象。如果找不到,就回溯到 object 类,之所以能回溯,是因为每一个对象都有一个字段引用了它的基类类型。从方法记录表中找到被调用方法的记录项后,进行JIT编译,编译完成后,在进行方法的调用。

    接下来调用的是Empoloyee的虚实例方法GetProgressReport。调用时,JIT要先在方法中生成一些额外的代码,每次都会执行这些代码。这些代码,首先检查发出调用的变量,然后跟随地址来到发出调用的对象,变量e引用的是 manager 对象,然后代码检查对象的内部的“类型对象指针”成员,改成员指向了实际的类型。然后在对类型的对象方法表中查找被调用的方法的记录向,对方法进行JIT编译,并且执行。

     Employee和Manager类型对象都会包含一个“类型对象指针”成员,由于类型对象本身也是对象,CLR在创建类型对象时,必须初始化这些成员。CLR开始在进程中运行时,会立即为MSCorLib.dll中定义的System.Type类型对象创建一个特殊的类型对象。Employee和Manager类型对象都是改类型的“实例”,因此,他们的类型对象指针成员都会初始化成对System.Type类型对象的引用。当然,System.Type类型对象本身也是对象,内部也有“类型对象指针”成员,这个指针就指向了他本身,因为这个System.Type类型对象本身是一个类型对象的“实例”。而System.Object的GetType方法返回存储在指定对象的“类型对象指针”成员中的地址,也就是说,GetType方法返回执行对象的类型对象指针,这样就可以判断任何对象的真实类型。

posted on 2019-03-05 22:35  恋那片海  阅读(456)  评论(0编辑  收藏  举报