[C#1] 3-基元类型、引用类型和值类型、装箱拆箱

1.基元类型

编译器直接支持的数据类型成为基元类型。基元类型与FCL中的类型有直接的映射关系[int=Int32],这样我们可以简化的方式书写代码,并且编译后的IL和直接使用FCL中的数据类型是完全相同的。

Checked和Unchecked操作:

Byte b=100;
b=(Byte)(b+200);

CLR只在32位和64位上进行算数运算,所以b首先会被转换为32位的值再和100相加,得到的是32位的值,接着转型为Byte,再然后将其放入b的存储堆栈。但是b的结果是44,反生了溢出,并不是期望的300[当然b也存不下300],然而却并没提示什么异常或错误。这是因为C#编译器默认是不检查溢出的。可以使用/checked+命令行开关。但是这是针对所有的代码都进行溢出检查,会试代码的效率有所下降。C#中的checked和unchecked操作符则提供了更好的灵活性。

b=checked((Byte)(b+200));这样再运行此行的话就会抛出 System.OverflowException[算术运算导致溢出]异常;相反unchecked则是不检查溢出,不会抛出异常。

System.Decimal是一个特殊的类型,虽然C#把它当作基元类型,但是CLR却不是,意味着CLR没有直接操作Decimal的IL指令,查看msdn中Decimal类型的文档可以发现它提供了Add、Divide、、、静态方法及一些操作符[+-*/...]重载方法,当我们使用它的值运算时实际上是调用它的成员来执行的,所以效率会比其他基元类型差些[CLR为其他基元类型直接提供了运算的IL指令,省去了操作符的重载],因为也没IL指令,所以checked和unchecked对它没有任何影响,如果对它的操作没有安全执行,则抛出System.OverflowException异常。

2.引用类型和值类型:

作为局部变量时值类型位于线程堆栈上,引用类型位于托管堆;作为类型成员时,则由其所属类型决定。托管代码中,我们定义的类型决定了它在内存中的分配位置,而我们对此没有控制的权限。另外值类型不受GC的控制。C#中值类型不允许定义Finalize方法[只有值类型装箱后才可能被调用],CLR允许,但是CLR执行垃圾清理时比不会调用它,所以为值类型定义Finaliza方法是没有意义的。

控制类型中字段的布局:System.Runtime.InteropServices.StructLayout特性来告诉CLR如何布局类型中的字段;C#编译器为引用类型选LayoutKind.Auto,让CLR自己排列字段。为值类型选择LayoutKind.Sequential,让CLR保留我们自己定义的字段布局。当然我们也可以通过此特性来改变编译器的默认行为。

3.装箱和拆箱

装箱过程:从托管堆中为新生成的引用类型对象分配内存[大小是值类型成员本身的大小加上附加成员的大小];再把值类型的实例字段拷贝到托管堆上新对象的内存中,然后返回对象的引用。

拆箱过程:获取指向对象中包含的值类型部分[数据字段]的指针,不会涉及字段拷贝。

然而紧接着拆箱之后的典型操作往往是字段拷贝。--所以装箱和[拆箱+字段拷贝]总体互反。另外装箱操作装进去什么类型就要拿什么类型来拆,不然你装进去一个大苹果[int],拆除一个小苹果[byte]、或者拆出一个string[桔子],你会愿意吗?装箱和拆箱\字段拷贝会从速度和内存上损伤程序的性能。尽量的避免这些操作

作者:Blackheart
上一篇:[C#1] 4-通用对象操作


下一篇:[C#1] 7-枚举