声明: 前面都是语言很基础的废话, 可直接跳到后面查看内容
官方文档的总结:Value Types and Reference Types
.Net下数据类型分为三大类︰
- Value Type (值类型) ︰常见的型别是struct、int、char、double等,基类System.ValueType,这些类型的大小是固定的。例如,声明一个int变量会使编译器分配4个字节的内存(32位)来保存整数值。
- Reference Type (参引用类型) ︰典型的例子就是使用class关键字定义的类型,基类Object类
- Pointer Type (指针类型):不安全代码,较少使用
C# (或说大部份的高级语言) 会将内存分为两大用途︰Stack 与Heap。
整理一下重点如下︰
- 在C# 中,内存用途分为Stack 与Heap 两种,所有的区域变数(不管是Value Type 或是Reference Type) 都储存于Stack 下,使用new 关键字实体化的类型实例,则储存于Heap 中
- Value Type 储存的是实际值,Reference 储存的是地址/引用
- 由于Value Type 与Reference Type 在内存储存值上的差异,在使用上若不理解,有时会造成意料外的问题
注意不准确的描述:值类型存储在栈上,引用类型存储在托管堆上。 准确的描述: 引用类型一定存储在托管堆上(目前为止是这样,后面内容会提到另外一种可能), 值类型可能存储在栈上,也可能存储在堆上,因为值类型作为引用类型的字段,肯定就存储在堆上了。
首先看下面书的中的总结
Microsoft Visual C# Step by Step (8th Edition)
int i = 42; // 声明并初始化i
int copyi = i; /* copyi包含i中数据的副本:
i和copyi都是值42 */
i++; /* 增加i对copyi没有影响;
我现在包含43,但copyi仍然包含42 */
Circle c = new Circle(42);
Circle refc = c;
WrappedInt wi = new WrappedInt();
Console.WriteLine(wi.Number);
Pass.Reference(wi);
Console.WriteLine(wi.Number);
public static void Reference(WrappedInt param)
{
param.Number = 42;
}
变量被作为参数向Pass.Reference方法。因为WrappedInt是一个类(引用类型),所以wi和param都引用同一个WrappedInt实例对象。通过Pass.Reference中的param变量对对象的内容所做的任何更改方法完成后,直接影响到wi变量。上图说明了将WrappedInt对象作为参数传递给Pass.Reference方法时发生的情况:
计算机内存的组织方式
计算机使用内存来保存正在执行的程序以及这些程序使用的数据。要了解值和引用类型之间的差异,了解内存中数据的组织方式将很有帮助。
诸如C#使用的操作系统和语言运行时经常将用于保存数据的内存划分为两个单独的区域,每个区域以不同的方式进行管理。传统上,这两个内存区域称为栈stack 和堆heap。栈和堆有不同的用途,在此进行描述:
-
调用方法时,总是从栈中获取其参数及其局部变量所需的内存。当方法完成时(因为它返回或引发异常),为参数和局部变量获取的内存将自动释放回栈,并在调用另一个方法时再次可用。方法参数和堆栈上的局部变量具有明确定义的生命周期:它们在方法开始时就存在,而在方法完成时就消失。
注意
实际上,相同的生命周期适用于在用花括号括起来的任何代码块中定义的变量。在以下代码示例中,变量i在while循环的主体开始时创建,但是在while循环结束时消失,并且在右括号之后继续执行:
while (...) { int i = ...; // i 在这里的栈上创建 ... } // i 从这里的栈中消失了
- 使用new关键字创建对象(类的实例)时,始终从堆中获取构建对象所需的内存。您已经看到可以使用引用变量从多个地方引用同一对象。当对对象的最后一个引用消失时,该对象使用的内存将成为垃圾(尽管可能不会立即对其进行回收)。因此,在堆上创建的对象的生存期更加不确定。使用new关键字创建一个对象,但是该对象仅在删除对该对象的最后一个引用之后的某个时间才会消失。
注意
所有值类型都在栈上创建。所有引用类型(对象)都在堆上创建(尽管引用本身在堆栈上(说的是变量))。可空类型实际上是引用类型,它们是在堆上创