数据项的类型决定了存储它所需的内存大小(多少位),以及该类型包含的数据成员;同时,类型也规定了对象在内存中的存放位置 是栈(Stack)还是堆(Heap)。
类型分为两大类:值类型(Value Types)与引用类型(Reference Types),两类对象在内存中的存储方式完全不同。
1、值类型仅占用一段内存,这段内存直接存储真实数据。

2、引用类型需要两段内存:
- 第一段存放真实数据,且永远分配在堆中;
- 第二段是一个引用地址,指向堆里数据所在的位置。(栈中存储堆中的地址(C语言时代叫指针))

以上两张图,仅仅是表示一个对象不属于其他对象的数据成员的时候,才符合
注意
引用类型对象的实体数据部分,永远存放在堆中,如上图。
但是值类型实例或者引用类型的引用地址,存储位置可能是堆也可能是栈,要看代码是如何。
例如:
假设有引用类型 Person,它包含两个成员 —— 一个值类型成员、一个引用类型成员。
很多人会误以为:值类型成员放栈、引用地址放栈,实体数据放堆(如上图),但这个结论是错误的。
记住一点:引用类型实例的全部实体数据永远在堆中。两个成员都属于这个对象的实体数据,因此无论成员本身是值类型还是引用类型,都会一并存放在堆内。
例子:
class Program
{
static void Main(string[] args)
{
int a = 10;
Person bill = new Person("Bill", 18);
}
}
class Person
{
private string name;
private int age;
private Phone myPhone;
public Person(string name, int age)
{
this.name = name;
this.age = age;
this.myPhone = new Phone();
this.myPhone.number = 12345;
}
}
class Phone
{
public long number;
}Code language: C# (cs)
看上面例子,Person里面的数据成员,除了内置的类型age,name,还有一个Phone,这个Phone本身又是一个自定义类型。这个自定义类型Phone里面又有一个内置类型long,用来存储手机号码。
读者可能看到Person里面的int age,或者Phone 里面的long number ,以为它们是值类型,就存储在栈里面,其实不是的,虽然这些是内置的基本类型,但是由于它们是属于一个class自定义类型里面的数据成员,它们会作为一个实例的数据存储在堆里面的。最终数据存储会是这样:

记住一点:自定义类型的数据成员都是存在在堆的,不管它是值类型还是引用类型,如果一个引用类型里面的数据成员例如上面的myPhone,对于Person里面存在的又是另外一个对象的地址。
如果上面Phone里面又有一个其他的自定义类型,那么Phone的数据里面又会有一个地方存在了另外一个自定义类型的地址(引用)。
整个程序就是从Main函数里面开始,第一个最顶层的引用类型的地址存在在栈里面,然后下面可能就像一条链一样,一层层地链接下去。

另外一个例子
// 引用类型 class
class MyType
{
public int A; // 值类型成员,存放在堆中MyType实例内部
public Person B; // 引用类型成员,引用指针存堆,实体数据另开堆空间
}
class Person
{
public string Name;
}
void Test()
{
// stack 存储 myObj 的引用指针
MyType myObj = new MyType();
/*
Heap:
MyType对象 {
A: int(直接内嵌)
B: 指向Person的引用指针
}
另一块Heap: Person{ Name: ... }
*/
// 局部值类型变量,直接存在栈
int localNum = 10;
}Code language: C# (cs)
以上说的数据,是自定义类型里面的数据成员,不是函数成员,如果一个类class,里面的函数成员又用到了局部变量或者引用,那么这些局部变量和引用类型实例的地址,还是会存在在栈的。例如下面代码:
再看下面的例子:
using System.ComponentModel.DataAnnotations;
class Program
{
static void Main(string[] args)
{
int a = 10;
Person bill = new Person("Bill", 18);
bill.Calculate();
}
}
class Person
{
private string name;
private int age;
private Phone myPhone;
public Person(string name, int age)
{
this.name = name;
this.age = age;
this.myPhone = new Phone();
this.myPhone.number = 12345;
}
public int Calculate()
{
int one = 12;
int two = 23;
Phone myNewPhone = new Phone();
return one + two;
}
}
class Phone
{
public long number;
}Code language: C# (cs)
我在之前的代码中,给Person增加了一个Calculate的函数,然后函数里面有局部变量,有值类型也有引用类型,然后main函数中调用。这些函数里面的数据,它们不属于Person的数据成员,它们属于函数里面的局部变量,对于这些局部变量,如果是值类型,就存储在栈中,如果是引用类型,那么它的地址(引用)也是会存在在栈的。

当执行bill.Calculate();这句代码的时候,内存是这样的

上图中,蓝色部分,就算执行函数的时候,这些函数内部的变量的存储方式了。
一旦函数执行完毕,这几个变量就会出栈的。 就会变成这样

函数Calculate执行完毕后,回到Main函数,那么在函数里面创建的变量,就会出栈销毁了,但是对于myNewPhone的数据部分,也就是上图中的蓝色的,它在堆里面,栈中出栈的只是地址(引用),真正的数据部分是不会马上销毁的,它还会保留在堆中,等待垃圾回收器GC,在某个时间回收它。