C#中类型主要分为引用类型和值类型,通常情况下值类型分配在堆栈(Stack)上,而引用类型则分配在托管堆(Heap)上。
下图列出了C#中的值类型和引用类型。如果想知道一个变量是值类型还是引用类型,可以采用variable.GetType().IsValueType
去检测。
下图列出了C#中的核心值类型和引用类型:
下面通过几个小例子演示内存的分配
Int
Int类型占用内存根据所在的作用域进行出栈和入栈。比如下面的代码。num1先行拿到值10位于stack上,之后因为跳出作用域,值10将会进行出栈,之后一次分配20,30给num2和num3。此时在尝试访问num1是不行的,因为值10已经出栈不存在了
在看下面这个代码
int num = 10;
int secNum = num;
secNum = 20;
Console.WriteLine($"{num},{secNum}");
首先分配10给num,这个时候将num赋值给secNum时,其实是将值复制了一份给secNum,也就是说在stack中,有两个10。 如果此时将secNum修改为20,并不会影响到num。所有最后的输出是 10,20
Array
Array值存处在Heap上,在Stack上存储了执行Heap的地址信息。 看下面这个例子 最后将会输出
1, 5
100, 5
100, 5
数组的传递其实是引用传递,所以当调用ChangeArray去修改数组的值时,Heap上面的将从2变成了5。 同理将第一个数组赋值给第二个数组,当修改了第二个数组的值时,也会影响到第一个数组的实际值,因为他们的值是同一份。
List
List内部实现采用的是数组,初始容量是4. 比如:这个list初始化存储1,2,3,4
var list = new List<int> { 1, 2, 3, 4 };
如果此时调用list.Add方法添加5,6
list.Add(5);
list.Add(6);
因为list初始容量4已经被用完,这个时候List内部数组将会从堆上进行Resize,新的容量是8,从而可以放得下5和6 如果想避免进行Reszie,同时预支将会需要多大容量,可以在New List时就指定容量。
int expectedLength = 6;
var list = new List<int>(expectedLength) { 1, 2, 3, 4 };
list.Add(5);
list.Add(6);
String
字符串是一种特别的引用类型,拥有同样的字符串值的变量其实是指向同一块Heap区域。 如果对字符串进行更改,那么新字符串值时重新分配的,不会更改原来字符串的值,也就是常说的immutable. 看下面这个代码
string str = "100";
string str2 = str;
Console.WriteLine(ReferenceEquals(str, str2));
str2 = "200";
Console.WriteLine(ReferenceEquals(str, str2));
Console.WriteLine($"{str},{str2}");
string strA = "A";
string strB = "B";
Console.WriteLine(ReferenceEquals(strA, strB));
strB = "A";
Console.WriteLine(ReferenceEquals(strA, strB));
将会依次输出:
true //同一个引用
false //str2被修改为200,是一份新地址
100,200 //
false //字符串值不一样,不同地址
true //字符串值变回A,又重新指向Heap上值为A的空间
值类型一定存储在栈(Stack)上?
前面我们提到值类型一般存储在栈上,但是也不是一定这样,一个特例就是值类型变量在引用类型class内部,那么其实它是随class一起分配到Heap上的。
public class ReferenceTypeClass
{
private int _valueTypeField;
public ReferenceTypeClass()
{
_valueTypeField = 0 ;
}
}