1.CLR最重要的特性之一就是类型安全
:
在运行时,CLR总是知道对象的类型是什么,调用GetType()
方法即可知道对象的确切类型。这也就是为什么GetType()
方法是基类Object
中的非虚方法,因为如果可以重写GetType()
方法,那么他返回的就不一定是当前的对象类型,违背了类型安全
。
2.使用 is 和 as 操作符来转型:
is
和as
在任何情况下都不会抛出异常。
public class People{}
public class Student : People{}
在做类型判断时,我们可以使用is
操作符
var p = new People();
var s = new Student();
Console.WriteLine($"{p is People}\t{p is Student}\t{s is People}\t{s is Student}");
// True False True True
在做类型转换时,使用as
操作符
// Use is
if(s is People) var pp = (People)s;------------①
// Use as
var pp = s as People;--------------------------②
值得注意的是
①中在if中判断一次后,使用强制转换时CLR
会再判断一次是否兼容,如果不兼容那么转换就会报错,所以在①中一共有两次判断。
②中有且只有CLR
来判断一次,并且在不兼容的情况下,会返回null而不会抛出异常。
所以我们在转化类型时,应该使用as
操作符,不仅性能高而且还安全。
3.命名空间和程序集的关系:
命名空间和程序集(实现类型的文件)
不一定相关。特别是,在同一命名空间
的类型可能在不同的程序集
中实现。例如:在同一命名空间System.IO
下的FileStream
类型和FileSystemWatcher
类型,其中FileStream
在MSCorLib.dll
程序集中实现,而FileSystemWatcher
在System.dll
程序集中实现。
当然,同一个程序集也包含不同命名空间中的类型。
4.线程栈:
已加载CLR的一个Windows进程可能有多个线程,在线程创建时分配到1MB
的栈,栈空间
用于向方法传递实参
,方法内部定义的局部变量
也在栈上。
5.线程执行一个方法:
一个简单的方法包含
序幕(prologue)代码
,在方法开始时工作前对其进行初始化,对方法内的局部变量分配内存,CLR对其变量初始化。尾声(epilogue)代码
,在方法完成工作后对其进行清理,以便返回至调用者。
6.堆上所有的对象都包含两个额外
成员:
类型对象指针(Type Object Pointer)
,任何时候在堆上新建对象,CLR都自动初始化内部的“类型对象指针”成员来应用和对象对应的类型对象。同步块索引(Sync Block Index)
,在调用类型的构造器(本质上是可能修改某些实例数据字段的方法)之前,CLR会先初始化同步块索引,并将对象的所有实例字段设为null或0
7.CLR创建类型对象时,必须初始化以上两个成员
我们在创建实例时
Student Hao = new Student();
Student Sin = new Student();
Teacher Jax = new Teacher();
在堆上如下
值得注意的是类型对象(Student、Teacher)
本质上也是对象,它们的类型对象指针也必须初始化。事实上CLR开始在一个进程中运行时,会立即为MSCorLib.dll
中定义的System.Type
类型创建一个特殊的类型对象,而Student、Teacher类都是他的实例
,所以结果如下
当然Type类型对象
也有自己的类型对象指针
,他指向自己
。
所以现在我们知道了,System.Object
的GetType
方法返回存储在指定对象的类型对象指针
成员中的地址
,也就是说,GetType
方法返回指向对象的类型对象指针
。
这样就可以判断系统中的任何对象了。
下面的运行结果可以验证