C# 值类型和引用类型
有一段讲解值类型和引用类型的段落很好。摘抄自一本书内,如下:
在分析C#中的值类型和引用类型之前,讲两个例子来使抽象的概念变得具体。
假设你在看一本书,你的朋友此时也想看你手上的那本书。为了让朋友看到,要么将自己受伤的书借给对方,要么再复制一本给他。无论如何做,都是对书本身进行操作,而复制后的两本书,完全是独立的,没有任何的关系。这种行为就可以类比C#中的值类型的行为。
换另一种假设,假设你正在看电视,换台、调音量等对电视的操作都通过使用电视遥控器,而不是直接对电视操作。若你的朋友要看别的电视台或者改变音量,无需把电视给他,只需要把遥控器给他就好了。这就是引用类型的行为。
C#中的大部分是引用类型,但在实际的开发过程中,值类型还是常用的。引用类型总是从托管堆分配,C#要求所有对象都使用new 操作符创建。
区别:最大的区别是,基于值类型的变量直接包含值。也就是说将一个值类型变量赋给另一个值类型变量时,将直接复制其包含的值,引用类型变量的赋值只是复制对象的引用。举一个栗子,若所有类型都是引用类型的,那么哪怕是一个数字都是引用类型的,也就是说使用一个数字1都是需要进行一次消耗巨大的内存分配。
另一个差别是,值类型不能派生其他的类型,所有值类型都是隐式密封的,这么做的目的是为了防止将值类型用作其他类型的基类型。
变量的值是如何分配的?通常说,变量的值分配的位置与声明该变量的位置有关。局部变量的值总是存储在线程栈上,实例变量的值和实例本身存储在实例存储的地方。引用类型实例和静态变量总是存储在堆上。
值类型的实例一般是分配在线程栈上而不是全部,是由于有些情况下,值类型也有可能分配在托管堆上。
这些特殊情况包括数组中的元素,引用类型中的值类型字段,迭代器块中的局部变量,闭包情况下匿名函数(Lamda)中的局部变量。此时,有可能出现的情况是,若该情况下的值类型实例如分配在线程栈上,有可能会出现线程栈中的方法已经调用结束,但还是会访问这些值的情况,值会随着调用方法的返回而被清除掉。因此它们被分配在托管堆上,以满足在方法返回之后还能被访问的要求。
因此,单纯的说“引用类型保存在托管堆上,值类型保存在线程栈上”是不准确的。准确来说,引用类型总是分配在托管堆上,但值类型并非总是分配在线程栈上。
任何被称为“类”的类型都是引用类型。特别注意的是,字符串string并非是一个实际的字符串,而是对字符串的一个引用,故而字符串是引用类型。
那么在C#语言规范或者微软官方的C#语言规范,任何呗称为“类”的类型都是引用类型,并且通常 使用以下关键字声明自己定义的引用类型;
- Class
- Interface
- Delegate
内置的一些引用类型,Dynamic,Object,string。。。
C#中常见的引用类型,如System.Collections.Generic.List类、System.Text.Decoder类。
与引用类型相对的就是值类型,可分为结构和枚举两种。
结构又分为三种:
- 数字型结构:System.Int32结构、System.Float结构、System.Decimal结构等。
- 布尔型结构:常见即System.Boolean等。
- 用户自定义的结构。
可当常识的一些点:C#中值类型:简单类型,结构类型,枚举类型;引用 类型:类,数组,接口。。。。
根据微软官方的语言规范文档,所有的值类型都必须派生自System.VlaueType。另,值类型有两种表示方式,分别是未装箱和已装箱。而装箱机制就是☞将值类型转换成引用类型。是由于值类型不在托管堆中分配,不被垃圾回收。但很多情况下,需要获取和操作对值类型实例的引用,此时装箱机制应运而生。
总结几个值类型的特征:值类型不派生出其他任何类型亦不需要从其他类型派生。 值类似不可变,且以值方式传递;