阅读《CLR via C#》总结(第二天)

4 篇文章 0 订阅

Ch4-类型基础

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

“运行时”要求每个类型都从System.Object类型派生,即:

//隐式派生自Object
class Employee{
........
}

//显示派生自Object
Class Employee : System.Object{
...
}

完全一致。

故所有类型的每个对象都保证了一组最基本的方法。

 CLR要求所有对象都用new操作符创建。
eg:

Employee e = new Employee("ConstructorParaml");

这个new操作符进行了如下操作:

  1. 计算类型及其所有基类型(一直到System.Object,虽然它没有定义自己的实例字段)中定义的所有实例字段需要的字节数。堆上每个对象都需要一些额外的成员——类型对象指针和同步索引块,https://www.cnblogs.com/dawenyang/p/7520263.htmlhttps://blog.csdn.net/weixin_30861459/article/details/97223966这两篇博客很清楚的描述了两者的定义及作用。
  2. 从托管堆中分配类型要求的字节数,从而分配对象的内存,分配的所有字节都设为零。
  3. 初始化对象的“类型对象指针”和“同步块索引”成员。
  4. 调用类型的实例构造器,传递在new调用中指定的实参,大多数编译器都在构造器中自动生成代码来调用基类构造器。每个类型的构造器都负责初始化该类型定义的实例字段,最终调用到System.Objec的构造器,该对象器什么都不做(因为它没有自己的实例字段),简单的返回

new执行完这些操作后返回一个引用(或指针)指向新建对象。

CLR采用垃圾回收机制,故无法显示释放为对象分配的内存。 

2.类型转换 

CLR最重要的特性之一为类型安全。在运行时,CLR总是知道对象的类型是什么

如果细看前面的类型对象指针就会明白,其实在初始化对象的类型对象指针的时候,会利用MSCorLib.dll中定义的System.Type类型创建一个特殊的类型对象,所有类型对象都是该类型的实例,它们的类型对象指针成员会初始化成对应的System.Type类型对象的引用

System.Object的GetType方法返回存储在指定对象的“类型对象指针”成员中的地址。也就是说,GetType方法返回指向对象的类型对象的指针。这样就可以判断系统中任何对象(包括类型对象本身)的真实类型。

由于GetType方法为非虚方法,所以一个类型不能伪装成另一个类型。eg:Employee类型不能重写GetType方法返回一个SuperHero类型。

public override Type GetType(){
...
return SuperHero;
}

上述代码是绝对不会编译通过的。

C#可以向其基类型对象隐式转换

Object o = new Employee();

但对象转换为它的派生类时,C#要求开发人员只能进行显示转换,因为可能存在转换失败

Employee e = (Employee)o;//这个o是上面定义的Object类型

在运行时,CLR会检查要转换的类型是否为其基类或其派生类,故即使使用Object传参这种方式“欺骗”编译器通过编译,运行时仍会报错。

public void ZhuanHuan(Object o)
{
    Employee e = (Employee)o;
}

public static void Main(){
    DataTime newYear = new DataTime(2020,1,8);
    ZhuanHuan(newYear);
}

上述代码运行时仍会报错,当然,一般也不会有人这么写代码。

C#的is和as操作符转型

直接看代码

Object o = new Object();
Boolean b1 = (o is Object); //b1为true
Boolean b2 = (o is Employee);  // b2为false

如果对象引用为null时,is总是返回false,因为没有可检查对象。

一般使用方式如下

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

值得注意的是CLR在上述代码中检查两次类型对象,第一次检查o是否兼容于Employee,如果是进入代码块,再次检查o是否引用一个Employee,可以看出其实对性能有一定影响。

由于上述代码的编程方式非常常用,为优化,C#专门定义了as操作符。

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

在这段代码中CLR只核实一次o是否兼容于Employee,如果是返回一个对象(非null)的引用,否则返回null。

使用is和as操作符进行类型转换时不会报出异常,要注意的是is操作符不会进行转换,只会返回true或false,as会进行转换但要判断是否为空。

as与is区别:

  1. as在转换的同时判断兼容性,无法转换返回null。
  2. as只能进行引用类型转换或装箱转换,值类型只能结合is
  3. 做转换时as比is快,少做一次类型兼容转换。
  4. is只是兼容性判断,不做转换

3. 运行时的相互关系

接触过编程一段时间后应该知道值类型和引用类型了,那么就该探究一下运行时,类型、对象、线程栈、托管堆之间的相互关系了。

 一个进程可能包含多个线程,线程创建时会分配1MB的栈,栈空间用于向方法传递实参,包括咱们定义的局部变量也在栈上,从高位内存地址向低位内存地址构建。

 如图,左边为当前线程的M1方法,右边为当前线程的栈(上面灰色区域带三个点的区域指代前面已经存储的变量等)。

当执行到第一句String name = "Joe";时,我们会在栈上分配变量name的内存。如图所示:

 执行到下一句代码时会调用M2函数,并将name作为实参传递,这造成name局部变量中的地址被压入栈,M2方法内部使用s标识栈位置,且在调用时会将返回地址压入栈内,提供返回的路径,如图所示:

 同理,M2方法执行时会把自己的变量length和tally依次压入栈内,直到执行到return代码

自此,依次出栈,返回M1变成进入M2之前的样子。 

注:执行到return代码时,CPU的指令指针会设置成栈中的返回地址,M2的栈帧(当前线程的调用栈中的一个方法调用,进行每个方法时都会在调用栈中创建并压入一个StackFrame)展开(按源著的说法就是将上述几副图中的栈当作一个线圈,栈中的参数变量等当作线去理解)差不多就如下图的感觉:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值