共计 1566 个字符,预计需要花费 4 分钟才能阅读完成。
CLR via C
第四章 类型基础
所有类型都是从 System.Object 派生
类型转换
命名空间和程序集
运行时的相互关系
4.1 所有类型都从 System.Object 派生
CLR 要求所有类型都用 System.Object 派生。
换句话说,所有类型都能够向上递归,显式转换为 System.Object 类
在转换过程中,如果涉及到值类型与引用类型的转换的时候,也同样会发生装箱操作。
CLR 要求所有对象都通过 new 操作符创建,这里面存在一个 new 生效的过程
计算类型及所有基类型中所有实例字段需要的字节数。
分配 1 中计算的字节数,从而分配内存,将所有分配的字节都设为 0
初始化对象的类型对象指针和同步索引块
调用类型的实例构造器(要一直往上走到 System.Object
没有与 new 操作符对应的 delete 操作符。因此无法 显式 地释放为对象分配的内存(涉及到 GC 操作
4.2 类型转换
CLR 保证了类型安全。
CLR 了解运行时的类型时什么是通过由 System.Object 类中的非虚方法 GetType 得到的。
CLR 允许对象转换为它的类型或者它的任何基类型。换句话说,子类型隐式转换为夫类型是 CLR 所允许的。而当父类型转换成子类型的时候,因为涉及到可能的内存分配问题,所以 CLR 中只允许进行显式转换。
is&as 操作符
这两个操作符,前者是进行类型检查,返回的是 Boolean 值
后者进行的是赋值操作,返回的是指向该对象的指针 or null
通过这两个能够快速的在类型安全的情况下进行类型检查与类型检查并将符合的类型进行赋值的操作。
4.3 命名空间与程序集
命名空间所进行的是 逻辑分组 在程序集层面,实际上命名空间所进行的应当是一个字符串替换的过程
因此我们必须要警惕尽量不要进行相同函数命名的过程。
在产生命名空间不同但是最后声明了一样的函数名的问题,实际上可以通过向前拓展对应的 namespace 来使得准确定位到某一个方法。
4.4 运行时的相互关系
当存在栈内函数的反复调用的时候,对于上一个调用者来说,实际上会在当前的线程栈上预留一个返回的路标,通过这个地址,能够在调用完 B 程序之后通过这个路标返回到 A 的线程栈中进行接下来的语句的执行。
第五章 基元类型、引用类型和值类型
编程语言的基元类型
引用类型和值类型
对象哈希码
dynamic 基元类型
基元类型实际上就是一种能够被编译器直接理解的短类型,它算是一种 define,但是最后的实现还是会落到 System 库中的对应代码去,如 string 和 String 实际上并没有差别,实际上前者在编译到 IL 代码之后和后者是一样的。
5.2 引用类型和值类型
两者最大的区别在于 生存位置 前者生存于托管堆中,后者生存于线程栈中
其他的区别大致上有两点 1. 不会受 GC 影响 2. 不会像引用类型一样实际上是通过指针来操作本身
引用类型的特点
内存必须通过托管堆分配
堆上分配的每个对象都有一些额外成员,这些成员必须初始化
对象中的其他字节总设为 0
从托管堆分配对象时,可能强制进行一次 gc 当声明一个新的引用类型,然后将现有的引用类型赋值给它,这个时候并不会触发一次新的引用类型的生成,因为这样子的名称实际上可以认为是一个存在与线程栈上面的指针,这个指针直接指向了托管堆中该引用类型的内存空间,也就是说,和之前生成的引用类型实际上没有区别,只是多了一种唤醒它的名称而已。对新的名称修改同样会造成本身类型的改变。
CLR 可以根据使用者指定进行不同的内存排列。
5.3 值类型的装箱和拆箱
当我们将值类型进行类型转化的时候,一旦转换为了引用类型,这个时候就会显式的发生装箱的操作,装箱结束之后这个值类型会存在一个在托管堆上面的副本,该副本就会变成一个引用类型。而当我们再次将这个副本转换为某个值类型的时候,则就会发生拆箱的操作。
因为装箱拆箱操作涉及到大量的内存申请和 gc 操作,所以实际上会造成非常大的时间浪费。