所有类型都从System.Object派生
- “运行时”要求所有类型都从System.Object派生
- 所有对象都用new操作运算符创建。
- 没有和new操作符对应的delete运算符。不能显式释放对象。
类型转换
- 运行时,CLR总是知道对象是什么类型。
- CLR允许对象隐式向上转型,显式向下转型。
- 在运行时,CLR检查转型操作,如果转换的类型不是对象的实际类型或者基类,那么会抛出InvalidCastException异常。
- is操作符检查对象是否兼容于指定类型。但是这样效率特别低,因为CLR检查了两次对象类型, is操作符检查了一次,内部转型又检查了一次
if( o is Employee){
Employee e = (Employee) o;
// use e
}
- as运算符简化了代码的写法,还提升了性能(只检查一次对象类型)。如果对象不能转型,则返回值为null。如果直接使用null赋值的引用,会抛出异常System.NullReferenceException。
Employee e = o as Employee;
if( e != null){
// use e
}
命名空间和程序集
- CLR对“命名空间”一无所知。访问类型时, CLR需要知道类型的完整名称以及该类型的定义在哪个程序集中。
- 检查类型定义时,编译器在 /reference 选项指定的程序集中检查。编译器扫描所有引用的程序集,查找类型定义,将程序集信息和类型信息嵌入到元数据中。
- 命名空间和程序集不一定相关。同一命名空间的类型可能在不同程序集中出现,反之亦然。
运行时的相互关系
- 线程创建的栈的大小默认为1MB (可以修改)
void M3(){
Employee e;
Int32 year;
e = new Manager();
e = Employee.Lookup("Joe");
year = e.GetYearsEmployed();
e.GetProgerssReport();
}
JIT编译器将M3的IL代码转换为CPU指令时,先根据M3内部引用的所有类型确认所有程序集已经加载,即Employee , Int32, Manager 以及String(因为”Joe”)。然后利用元数据,CLR提取类型有关的信息,创建一些数据结构表示自身,(创建类型对象,即Type类型实例化的对象),但没有编译函数。
然后再执行代码创建一个Manager对象。该对象也有类型对象指针和同步索引块,以及包含必要的字节容纳Manager类型定义的所有实例数据字段,以及任何基类的实例字段。
- 堆上的对象都有两个额外的成员:类型对象指针和同步块索引。类型对象指针指向该对象的类型。即 e = new Manager(); e中的类型对象指针会指向Manager类型对象(Type类型实例化的对象),Manager类型对象中的类型对象指针指向 Type类型对象,Type类型对象指向自身。
- 虽然CLR会自动将所有局部变量初始化为null或0,但是如果代码试图访问未显式初始化的局部变量,C#会报告错误消息: 使用了未赋值的局部变量
- 调用静态方法时,CLR先定义静态方法的类型,然后在该类型的方法表中查找方法,对方法编译。
- 调用非虚实例方法,JIT会回溯类层次结构,在每个类中查找方法,并编译。
- 调用虚实例方法时,JIT先确认变量类型(使用类型对象指针),再在类型对象方法表中查找被调用的方法,并编译。