类型与通用语言运行时
5.基元类型、引用类型与值类型
编译器直接支持的数据类型称为基元类型primitive type.
基元类型和.NET框架类库FCL中的类型有直接的映射关系,对于那些和CLS兼容的类型,其他语言也都提供了类似的基元类型。
提倡使用FCL类型名。
编译器能够在基元类型之间进行隐式或者显示的转型,如果转型存在潜在的不安全(造成数据丢失),则要求显示转换System.Convert
关于C#溢出的检查,默认情况下是关闭的;
C#编译器提供了/checked+命令行开关来控制溢出检查。
同时,C#也提供了checked 和 unchecked操作符,及相应的语句;
Byte b=100;
b=checked((byte)(b+200));
checked{
Byte b=100;
b=(byte)(b+100);
}
//checked操作符和语句只影响加、减、乘以及IL指令产生的版本,在checked操作符和语//句内调用一个方法并不会对该方法产生任何影响。
当开发应用程序时,应使用编译器的/checked+命令行开关,此时系统会对任何代码做溢出检查。
CLR不会把System.Decimal看做一个基元类型,当编译使用Decimal值的代码时,编译器产生的代码实际上会通过调用Decimail类型的成员来执行相关操作,效率比操作其他基元类型低;另外,由于没有操作Decimal值的IL指令, checked 和 unchecked操作符和语句,及相关编译器命令行开关对它没有任何影响,不安全的操作都会抛出System.OverflowException
CLR支持的两种类型:
引用类型:从托管堆分配;C#的new操作符返回的就是对象位于托管堆中的内存地址
在使用引用类型时有一些性能需要考虑:
a.内存必须从托管堆中分配
b.每个在托管堆中分配的对象都有一些与之相关的额外附加成员必须被初始化
c.从托管堆中分配对象可能会导致执行垃圾收集。
值类型:线程的堆栈上;表示值类型实例的变量不包含指向实例的指针,因此变量本身包含了实例的所有字段而无需解析指针引用。值类型实例不受垃圾收集器的控制,因此也减少了托管堆的压力,及在整个生存周期中需要垃圾回收的次数。
所有的值类型都必须继承自System.ValueType类型。
任何被称为“类class”的类型都是引用类型;而将“结构struct”、“枚举”称作值类型。
Struct SomeVal {public int32 x;}
SomeVal v1=new SomeVal(); //代码看起来像是在托管堆上分配了一个SomeVal实例,然而C#编译器会知道SomeVal是一个值类型,因此会在线程的堆栈上分配SomeVal实例。C#还会确保该值类型实例所有字段都被置为0(二进制)
SomeVal v1; //也OK,二者唯一的区别是,如果使用了new,那么C#将认为实例已经得到了初始化。
e.g.
SomeVal v1=new SomeVal();
Int32=v1.x; //correct
SomeVal v1;
Int32=v1.x; //error
值类型与基元类型的比较:
a.值类型所有方法都隐含为密封sealed,即不能重新,不可以有任何抽象方法。
b.引用类型变量包含着对象在托管堆中的内存地址,创建时初始化为null;而值类型变量总是包含它的类型的值,且所有字段都被初始化为0值。
c.值类型变量赋值是“字段”拷贝,而引用类型是“内存地址”拷贝。
d.……
在很多情况下,我们需要获得一个指向值类型实例的引用,就得将一个值类型转换成一个引用类型(boxing)。
装箱操作通常有三步:
1.从托管堆中为新生成的引用类型对象分配内存。内存大小为值类型实例本身的大小、方法表指针和一个SyncBlockIndex总和。
2.将值类型实例的字段拷贝到托管堆新分配对象的内存中
3.返回托管堆中新分配对象的地址。
拆箱操作步骤:
1.若该引用是null,或者引用指向的对象不是一个期望的值类型的已装箱对象,抛出异常
static void Main(){
Int32 x=5;
Object o=x;
Int16 y=(int16) o; //拆箱时,转型的结果必须是它原来未装箱时的类型
Int16 y=(Int16)(Int32)o;
}
2.一个指向包含在已装箱对象中值类型部分的指针被返回(unboxing,拆箱操作结束)
3.字段拷贝
装箱和拆箱/拷贝操作对应用程序性能所产生的影响:
static void Main(){
Point p;
p.x=p.y=1;
Object o=p;
P=(Point)o; //将Point的x字段改为2
p.x=2;
o=p;
}
托管C++允许对一个已装箱的值类型实例执行拆箱操作时,不必拷贝其中的字段,这些操作将直接返回已装箱对象的值类型部分的地址,我们可以利用该指针来操作其值类型部分的字段。
Public static void Main(){
Int32 v=5;
Object o=v;
V=123;
Console.WriteLine(v+”,”+(Int32) o); //装箱3次
Console.WriteLine(v+”,”+o); //装箱2次
Console.WriteLine(v.ToString()+”,”+o); //装箱1次
}
又一例: //调用一个从基类继承而来的方法,需要一个指向其类型方法表的指针
Public static void Main(){
Point p;
p.x=10;
p.y=20;
Console.WriteLine(p.ToString()); //此处装箱
}
6.通用对象操作
<对象的等值性、唯一性……待补充>
一个类必须确定是否允许它的实例被克隆,如果希望,该类应该实现Icloneable接口:
Public interface Icloneable{
Object Clone();
}
浅拷贝shallow copy:当对象的字段值被拷贝时,字段引用的对象不会被拷贝;
深拷贝deep copy:对象实例中字段引用的对象一起拷贝;
将clone()方法实现为浅拷贝:
Public Object Clone(){
Return MemberwiseClone();
} // MemberwiseClone方法首先会为新对象分配内存,新对象的类型和this指针引用的对象类型相同;然后,遍历类型中所有的实例字段,并将原对象中所有的位拷贝到新对象中。
深拷贝的话:一般分配一个新对象