C#中的值类型和引用类型
在.NET中做的一切其实都是在和值类型或者引用类型打交道 ——《深入理解C#》
现实世界中的值和引用
假设你在读一份非常棒的东西,希望你的朋友也去读它。那么需要为朋友提供什么才能让他读到这份资料呢?这完全取决于阅读的内容。
先假设你读的是一份真正的报纸。为了给朋友一份,需要影印报纸的全部内容并交给他。在这种情况下,我们处理的是值类型的行为。制作了副本后,各自的信息相互独立。
再假设你正在读的是一个网页。与前一次相比,这一次,唯一需要给朋友的是网页URL。这是引用类型的行为,URL代替引用。假如网页某个内容发生变化,你和朋友看见的页面都会更改。
在C#和.NET中,值类型和引用类型的差异与现实世界中差别类似。.NET中的大多数类型都是引用类型,你以后创建的引用类型极有可能比值类型多很多。除了一下总结的特殊情况,类(class声明)是引用类型,而结构(struct声明)是值类型。特殊情况包括如下方面:
- 数组类型是引用类型,即使元素类型是值类型(int是值类型,int[]仍是引用类型)
- 枚举(enum声明)是值类型
- 委托类型(delegate)是引用类型
- 接口类型(interface)是引用类型,但可用值类型实现
值类型和引用类型基础知识
在学习值类型和引用类型时,要掌握的重要概念是一个特殊表达式的值是什么。为使问题更加具体,这里使用了表达式最常用的例子——变量。但是,同样的道理也适用于属性、方法调用、索引器和其他表达式。
对于值类型的表达式,它的值就是表达式的值,例如:表达式”2+3”的值就是5。
然而,对于引用类型的表达式,它的值是一个引用,而不是该引用所指代的对象。所以表达式String.Empty的值不是一个空字符串,而是「空字符串的引用」。
为了进一步演示这个问题,以下面为例:
Point p1 = new Point(10, 20);
Point p2 = p1;
变量的值在它声明时的位置存储。局部变量的值总是存储在栈中,实例变量的值总是存储在实例本身存储的地方。引用类型实例总是存储在堆中,静态变量也是。
两种类型的另一鞥差异在于,值类型不可以派生出其他类型。这将导致的一个结果是,值不需要额外的信息来描述实际是什么类型,而引用类型的开头都需要包含一个数据块,它标识了对象的实际类型,同时还提供了一些其他信息。
装箱和拆箱
有时,我们就是不想使用值类型的值,就是想用一个引用。幸好,C#和.NET提供了一个装箱的机制,它允许根据值类型创建一个对象,然后使用对这个新对象的引用。在接触实际例子时,先来回顾两个重要事实:
对于引用类型的变量,它的值永远是一个引用;
对于值类型的变量,它的值永远是该类型的值。
int i = 5;
object o = i;// 装箱
int j = (int)o;// 拆箱
i是值类型的变量,而o是引用类型的变量。将i的值赋值给o,这合理吗?
o的值必须是一个引用,而i的值是5。实际发生的情况就是装箱:CLR将运行在堆上创建一个新的对象,而o的值就是对新对象的引用,o对象的值就是对原始值的副本。
第三行执行相反的操作就是拆箱。必须告诉编译器将object拆箱成什么类型,否则会抛出异常。