1. 大体介绍
微软自从转型.NET Core、.NET统一版后,技术日新月异,现在已经去到C# 11和.NET 7了。所谓温故知新,做了个控制台探讨一下值类型和引用类型的区别,还有string字符串为何是特殊的引用类型。
下图展示了值类型和引用类型分别包括的数据类型:
2. 先基于NET 7做个小控制台,测试赋值问题
Console.WriteLine("值类型:int");
int i1 = 1111;
int i2 = i1;
Console.WriteLine($"i1:{i1},i2:{i2}");
i1 = 3333;
Console.WriteLine($"i1:{i1},i2:{i2}");
Console.WriteLine();
Console.WriteLine("引用类型:string");
string s1 = "aaaa";
string s2 = s1;
Console.WriteLine($"s1:{s1},s2:{s2}");
s1 = "bbbb";
Console.WriteLine($"s1:{s1},s2:{s2}");
Console.WriteLine();
Console.WriteLine("自定义类:class");
CustomClass c1 = new CustomClass(1111);
CustomClass c2 = c1;
Console.WriteLine($"c1:{c1.Value},c2:{c2.Value}");
c1.Value = 3333;
Console.WriteLine($"c1:{c1.Value},c2:{c2.Value}");
Console.WriteLine();
public class CustomClass
{
public int Value { get; set; }
public CustomClass(int value)
{
Value = value;
}
}
输出结果如图:不出所料,i2值没变,c2值变了,问题就出现在s2,说好的string是引用类型,为什么s1变了,s2缺没变?这就涉及到string是特殊的引用类型问题了
引用其中一段:字符串是不可变的,即:字符串对象在创建后,其内容不可更改。 例如,编写此代码时,编译器实际上会创建一个新的字符串对象来保存新的字符序列,且该新对象将赋给
b。 已为b分配的内存(当它包含字符串“h”时)可用于垃圾回收。
结论:字符串是不可变的,即属于只读实例,因此当字符串改变后,相当于在内存中分配一个新的对象保存数据,即s1="bbbb"相当于s1=new string("bbbb")。这里就能解析,当进行大量的字符串拼接时,为什么要使用StringBuilder进行拼接了。
3. 再测试集合下,数据类型处理的问题
Console.WriteLine("List<int>:");
var iList = new List<int> { 1 };
Console.WriteLine(iList[0]);
iList.ForEach(i =>
{
i++;
});
iList[0]++;
Console.WriteLine(iList[0]);
Console.WriteLine();
Console.WriteLine("List<string>:");
var sList = new List<string> { "a" };
Console.WriteLine(sList[0]);
sList.ForEach(i =>
{
i = i + "b";
});
for (int i = 0; i < sList.Count; i++)
{
sList[i] = sList[i] + "c";
}
Console.WriteLine(sList[0]);
Console.WriteLine();
Console.WriteLine("List<CustomClass>:");
var cList = new List<CustomClass> { new CustomClass(1) };
Console.WriteLine(cList[0].Value);
cList.ForEach(i =>
{
i.Value = i.Value + 1;
});
for (int i = 0; i < cList.Count; i++)
{
cList[i].Value = cList[i].Value + 1;
}
Console.WriteLine(cList[0].Value);
Console.WriteLine();
public class CustomClass
{
public int Value { get; set; }
public CustomClass(int value)
{
Value = value;
}
}
输出结果如下图所示,List<int>和List<CustomClass>的结果是显然的,值类型的集合在ForEach里面的操作不影响原本的集合,而引用类型则会影响,但string是属于特殊的引用类型,是不受影响的。
为什么不受影响?那就要看这里关键步骤IList<T>.ForEach()方法是如何处理,先查看微软官方的list.cs的源代码。从源代码可以看到,这里ForEach()操作的是_items[i]变量
该变量是一个新创建的私有变量,并且在构造函数时,使用CopyTo()完成了创建
这里又涉及一个问题,CopyTo是一个浅层次拷贝,与第一个例子实现的原理是相同的:
| 类型 | 栈中新增变量 | 托管堆内存分配新对象 |
|---|---|---|
| 值类型 | 是 | 值类型没有分配 |
| 引用类型 | 是 | 否 |
| string | 是 | 是 |