.Net框架程序设计-读书笔记(第五章 基元类型、引用类型和值类型)

1. 基元类型、引用类型和值类型
1.1. 基元类型
编译器直接支持的类型称为基元类型(primitive type)。基元类型和.NET框架类库中的类型有直接的映射关系。
1)       编译器能够在基元类型之间进行隐式或者显式的转型;
2)       基元类型能够以文本常量(literals)的形式出现;
3)       如果表达式中含有文本常量,编译器能够在编译时计算该表达式,以提高代码性能;
4)       编译器会自动解析出现在代码中的操作符;
CLR只在32位和64位值上进行算术运算。C和C++不把溢出认为是一种错误,并且允许对发生溢出的值做绕回(wrap)处理。而VB则把溢出视为一种错误。CLR提供IL指令允许编译器选择自己期望的行为。C#允许开发人员自己决定应该如何处理溢出。
1)       CLR提供一类指令,在执行运算时做溢出检查;
2)       C#编译器使用/checked+命令开关,checked和unchecked操作符,checked和unchecked语句,来执行溢出检查;
3)       checked、unchecked操作符和语句都不会影响其中调用方法的行为;
Decimal是一种非常特殊的类型,CLR没有直接操作Decimal值的IL指令。
1.2. 引用类型和值类型
CLR支持两种类型:引用类型和值类型。引用类型实例总是从托管堆上分配,而值类型实例通常在线程的堆栈上分配。在托管代码中,类型决定了类型实例的分配位置,而使用类型的开发人员对此没有控制权。任何被称为“类”的类型都是引用类型,“结构”或者“枚举”为值类型。
引用类型:
1)       内存必须从托管堆中分配;
2)       每个在托管堆中分配的对象,都有一些与之相关的额外附加成员必须被初始化;
3)       从托管堆中分配的对象可能会导致执行垃圾收集;
值类型
1)       CLR不允许一个值类型被用作任何其它引用类型或值类型的基类型,但可以为一个值类型实现一个或多个接口;
2)       C#允许不使用new关键字生成值类型实例,区别是如果使用了new关键字,那么C#认为实例已经得到了初始化,否则C#会确保值类型的所有字段都被置为0;
引用类型和值类型的区别:
1)       值类型对象有两种表示:未装箱形式和装箱形式,而引用类型总是装箱形式;
2)       当定义自己的值类型时,应该重写Equals方法和GetHashCode方法;
3)       值类型中不可以有任何的抽象方法,不可以引入任何新的虚方法,所有的方法都隐含为sealed方法;
4)       当一个引用类型变量被创建时,它被初始化为null。值类型变量总是包含一个符合它的类型的值;
5)       当将一个值类型变量赋值给另一个值类型变量,会进行一个字段对字段的拷贝,而将一个引用类型变量赋值给另一个引用类型变量时,只会拷贝内存地址;
6)       两个或多个引用类型变量可以指向托管堆中的同一个对象,而每个值类型变量都有一份自己的对象数据拷贝;
7)       值类型实例在内存回收时不可能收到任何通知;
System.Runtime.InteropServices.StructLayout特性用于指示CLR是按指定的顺序来存储类型实例的字段,还是以任何CLR认为合适的顺序排列字段。C#编译器为引用类型选择的是LayoutKind.Auto方式,而为值类型选择的是LayoutKind.Sequential方式。
1.3. 值类型的装箱和拆箱
.NET框架中用来将值类型转换为引用类型的机制称为装箱(boxing)。装箱操作由以下几步组成:
1)       从托管堆中为新生成的引用类型对象分配内存;
2)       将值类型实例的字段拷贝到托管堆上新分配的内存中;
3)       返回托管堆中新分配的对象的地址;
拆箱操作由以下几步组成:
1)       如果该引用为null,将会抛出一个NullReferenceException异常;
2)       如果该引用指向的对象不是一个期望的值类型的已装箱对象,就将会抛出一个InvalidCaseException异常;
3)       一个指向包含在已装箱对象中值类型部分的指针被返回;
拆箱操作仅仅是获取指向对象中包含的值类型部分的指针而已,它不会拷贝任何字段。但紧接着拆箱操作之后的操作往往是字段拷贝,这两个操作合起来与装箱操作才成为真正的互反操作。
public statics void Main(){
    Int32 v = 5;
    Object o = v;                                                 
    v = 123;
Console.WriteLine(v + “, “ + (Int32)o);                        
①对v进行装箱。②中,首先v被装箱,接着o被拆箱,并用一个临时的Int32值类型变量存放未装箱部分的拷贝,最后这个临时变量被装箱。
1)       因为未装箱值类型没有SyncBlockIndex,所以不能用System.Threading.Monitor类型来同步多个线程对它们的访问;
2)       因为未装箱值类型没有方法表指针,所以不可能通过值类型的未装箱实例来调用其上继承而来的虚方法;
3)       将一个未装箱的值类型实例转型为一个该类型实现的接口类型也需要装箱;
struct Point : ICloneable{
    public Int32 x, y;
    public override String ToString(){
        return String.Format(“({0}, {1})”, x, y);
    }
    public Object Clone(){ return MemberwiseClone(); }
}
class App{
    static void Main(){
        Point p;
        p.x = 10;
        p.y = 20;
        Console.WriteLine(p.ToString());                         ①
        Console.WriteLine(p.GetType());                         ②
        Point p2 = (Point)p.Clone();                                  ③
        ICloneable c = p2;                                                  ④
        Object o = c.Clone();                                              ⑤
        p = (Point)o;                                                             ⑥
    }
}
上面的代码中:
1)       正常情况下,调用一个从基类继承而来的方法,需要一个指向其基类型方法表的指针,但由于Point重写了ToString方法,而且编译器知道值类型不会出现多态行为,因此将产生直接调用ToString的指令;
2)       调用GetType时必须有一个指向方法表的指针,所以需要装箱;
3)       由于Point重写了Clone方法,它将产生直接调用Clone的指令,但需要对返回的Object对象进行拆箱;
4)       当p2被转型到一个接口类型时,p2必须被装箱;
5)       由于Point重写了Clone方法,所以c不必被装箱;
6)       当o被转型为Point时,被o引用的对象需要拆箱操作;

你可能感兴趣的:(.Net框架程序设计-读书笔记(第五章 基元类型、引用类型和值类型))