C#类型系统—值类型和引用类型
1 C#类型系统
C#是一种强类型语言。C#的类型系统是静态的、安全的,并且大多数时候是显式的。C#要求所有类型全部从System.Object类派生。所有类型都有一套System.Object所声明的方法:Equals、GetHashCode、ToString、GetType、MemberwiseClone、Finalize。
在U3D中,C#语言脚本接口是以MonoBehaviour这个类作为基础的,MonoBehaviour也派生自System.Object。
C#语言是静态类型的,即每一个变量都有一个特定的类型,该类型在编译时是确定的。
C#语言是安全类型,允许合理的类型转换,但是不能将两个完全不同的类型互相转换。C#中有隐式类型转换和显式类型转换两种形式。
对于.NET中的类型系统,有两个基本点:
①支持继承原则。
②每种类型被定义为值类型或引用类型。
2 值类型
值类型派生自System.ValueType(派生自System.Object)。值类型变量直接包含其值。对于值类型变量,没有单独的堆分配或垃圾回收开销。
值类型分为struct (数值类型、bool型、自定义结构体)和 enum两类。
所有的值类型都是隐式密封的,即无法派生出新的值类型。故值类型无须提供额外的信息来表明它的实际类型,没有引用类型中的额外信息。所以相对于引用类型,值类型的使用缓解了托管堆的压力,减少了消耗巨大的垃圾回收次数。
值类型的实例一般分配在线程栈而非全部分配在线程栈上。特殊情况下,值类型也可能分配在托管堆上,包括数组中的元素、引用类型中的值类型字段、迭代器块中的局部变量、闭包情况下匿名函数中的局部变量等。
值类型实例有两种表示方式,即未装箱和已装箱。
3 引用类型
定义为class、record、delegate、数组或interface的类型是引用类型。
无法使用new运算符直接实例化interface,而是创建并分配实现接口的类实例。
引用类型总是从托管堆分配,C#要求所有的对象都使用new操作符创建,new操作符返回指向对象数据的内存地址。new操作符做的事情如下:
①计算所需内存空间,new操作符会计算目标类型和包括System.Object类在内的,其所有基类中定义的所有实例字段所需要的字节数。除此之外,为了方便Mono运行时管理对象,还有一些额外的信息需要托管堆为其分配空间,如类型对象指针和同步索引块。
②完成计算对象所需的空间后,就要为对象在托管堆上分配所需的内存空间了,分配的所有字节都设为0。
③内存空间分配完后,初始化对象的类型对象指针以及同步块索引。
④最后调用类型的实例构造器。最终一定会调用System.Object的构造器,而该构造器仅仅是返回,没有什么逻辑操作。
⑤最后返回新建对象的一个引用。即新建变量是一个指向某类型对象的引用,而非对象本身。
4 值类型和引用类型小结
范围方面:
值类型包括:结构体(数值类型、bool型、用户定义的结构体)、枚举、可空类型。
引用类型包括:数组、用户定义的类、接口、委托、object、字符串。
内存分配方面:
值类型一般分配在线程栈而非全部分配在线程栈。
引用类型总是从托管堆分配。
值类型和引用类型都继承自System.Object类。不同的是几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。