值类型与引用类型,实际影响中,哪个是真正的幕后黑手?
摘要:大家好,我是刚子。 说实话,写代码这么多年,我发现一个挺有意思的事儿:面试的时候,问“值类型和引用类型有什么区别”,大家都能答上来——什么栈啊堆啊,值传递引用传递啊,背得比我都溜。 但一到真写代码,就翻车。 要么改了半天对象发现没改对,要么
大家好,我是刚子。
说实话,写代码这么多年,我发现一个挺有意思的事儿:面试的时候,问“值类型和引用类型有什么区别”,大家都能答上来——什么栈啊堆啊,值传递引用传递啊,背得比我都溜。
但一到真写代码,就翻车。
要么改了半天对象发现没改对,要么程序跑得卡得不行还不知道为啥。其实说白了,就是没搞明白这俩玩意儿在实际开发里到底怎么影响咱们的代码。
今天咱不背概念,就聊聊我这些年踩过的坑,给你说说这4个你肯定遇到过(或者迟早会遇到)的实际影响。
1. 赋值:一个是给钱,一个是给钥匙
这个是最基础的,但也是最容易犯迷糊的。
值类型赋值,就是给钱。
你想啊,我给你100块钱,你拿去买东西了,我兜里那100块钱还在不?肯定在啊。咱俩的钱各是各的。
代码里也是这样:
inta =10;intb = a;b=20;Console.WriteLine(a); // 还是10
a是10,b是另一个10,你改b,a纹丝不动。各自安好,互不打扰。
引用类型赋值,是给钥匙。
我给你配了把我家门的钥匙,你用钥匙开门进去把电视搬走了,等我一回家——哎我电视呢?
代码里就是这样:
varlist1 = new List<int> {1,2,3};varlist2 = list1;list2.Add(4);Console.WriteLine(list1.Count); // 变成4了
你本来以为我只是给list2加了个数,结果list1也被改了。
刚子划重点:碰到类、数组、List这种引用类型,赋值的时候心里得有根弦——这给出去的是钥匙,不是钱。不想被改?要么用new重新造一个,要么用ToList()拷一份出来。
2. 传参:我为啥改不了外面的变量?
这个更坑人。很多人以为“引用类型传进去就能改”,结果一写就懵。
我直接给你看个例子:
voidChangeValue(intx){ x =100; }voidChangeList(List<int> list){ list =newList<int>(); }
intnum =10;ChangeValue(num);Console.WriteLine(num);// 还是10,没变
varmyList =newList<int> {1,2,3};ChangeList(myList);Console.WriteLine(myList.Count);// 还是3,咋也没变??
这时候你就纳闷了:不是说引用类型能改吗?为啥我这list也没变?
其实道理特简单:引用类型传进去的,是钥匙的复印件。
你用复印件去开门,当然能改房子里的东西(比如list.Add没问题)。但你想换一把新钥匙(list = new List<int>()),你换的是复印件,原件还在我手里呢,当然改不了。
刚子划重点:
想改引用类型里面的内容,随便改,没问题。
想改变量本身(比如重新new一个),必须加ref。
voidChangeListReal(refList<int> list){ list =newList<int>(); }ChangeListReal(refmyList);// 这下myList真的变了
3. null:谁可以为空,谁不行
你肯定见过这个报错:Nullable object must have a value。这啥意思?
值类型天生不能是null。
你想啊,int就是整数,它怎么可能“没有数”呢?要么是0,要么是1,不存在“没值”这种状态。你想让它能空,得用int?,这叫“可空值类型”。
引用类型天生就能是null。
string可以是null,List可以是null,这也是为啥老报NullReferenceException的原因——你忘了判断它是不是空就直接用了。
不过这里有个小细节:
string? name =null;// 这个没问题,就是提醒你别忘了判空int? age =null;// 这个也没问题,表示“年龄未知”intage2 =null;// 这个编译不过,直接报错
刚子划重点:写代码的时候,值类型想表达“没有值”,用int?、DateTime?。引用类型别动不动就null,该初始化就初始化,不然线上崩了你都不知道咋回事。
4. 性能:一个能把程序拖垮的细节
这个新手基本不会注意,但老鸟都知道。
简单说:值类型大多在栈上,引用类型在堆上。堆上的东西需要垃圾回收(GC)。
GC一干活,你的代码就得暂停。一次两次没事,但如果你在循环里new一堆引用类型对象,GC频繁触发,程序就一卡一卡的。
