值类型与引用类型:别再只背“栈和堆”了,看这 4 个实际影响

大家好,我是刚子。

说实话,写代码这么多年,我发现一个挺有意思的事儿:面试的时候,问"值类型和引用类型有什么区别",大家都能答上来------什么栈啊堆啊,值传递引用传递啊,背得比我都溜。

但一到真写代码,就翻车。

要么改了半天对象发现没改对,要么程序跑得卡得不行还不知道为啥。其实说白了,就是没搞明白这俩玩意儿在实际开发里到底怎么影响咱们的代码。

今天咱不背概念,就聊聊我这些年踩过的坑,给你说说这4个你肯定遇到过(或者迟早会遇到)的实际影响。


1. 赋值:一个是给钱,一个是给钥匙

这个是最基础的,但也是最容易犯迷糊的。

值类型赋值,就是给钱。

你想啊,我给你100块钱,你拿去买东西了,我兜里那100块钱还在不?肯定在啊。咱俩的钱各是各的。

代码里也是这样:

复制代码
int a = 10;int b = a;b = 20;Console.WriteLine(a); // 还是10

a是10,b是另一个10,你改b,a纹丝不动。各自安好,互不打扰。

引用类型赋值,是给钥匙。

我给你配了把我家门的钥匙,你用钥匙开门进去把电视搬走了,等我一回家------哎我电视呢?

代码里就是这样:

复制代码
var list1 = new List<int> { 1, 2, 3 };var list2 = list1;list2.Add(4);Console.WriteLine(list1.Count); // 变成4了

你本来以为我只是给list2加了个数,结果list1也被改了。

刚子划重点:碰到类、数组、List这种引用类型,赋值的时候心里得有根弦------这给出去的是钥匙,不是钱。不想被改?要么用new重新造一个,要么用ToList()拷一份出来。


2. 传参:我为啥改不了外面的变量?

这个更坑人。很多人以为"引用类型传进去就能改",结果一写就懵。

我直接给你看个例子:

复制代码
void ChangeValue(int x) { x = 100; }void ChangeList(List<int> list) { list = new List<int>(); }
int num = 10;ChangeValue(num);Console.WriteLine(num); // 还是10,没变
var myList = new List<int> { 1, 2, 3 };ChangeList(myList);Console.WriteLine(myList.Count); // 还是3,咋也没变??

这时候你就纳闷了:不是说引用类型能改吗?为啥我这list也没变?

其实道理特简单:引用类型传进去的,是钥匙的复印件。

你用复印件去开门,当然能改房子里的东西(比如list.Add没问题)。但你想换一把新钥匙(list = new List<int>()),你换的是复印件,原件还在我手里呢,当然改不了。

刚子划重点:

  • 想改引用类型里面的内容,随便改,没问题。

  • 想改变量本身(比如重新new一个),必须加ref。

    void ChangeListReal(ref List<int> list) { list = new List<int>(); }ChangeListReal(ref myList);// 这下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; // 这个也没问题,表示"年龄未知"int age2 = null; // 这个编译不过,直接报错

刚子划重点:写代码的时候,值类型想表达"没有值",用int?、DateTime?。引用类型别动不动就null,该初始化就初始化,不然线上崩了你都不知道咋回事。


4. 性能:一个能把程序拖垮的细节

这个新手基本不会注意,但老鸟都知道。

简单说:值类型大多在栈上,引用类型在堆上。堆上的东西需要垃圾回收(GC)。

GC一干活,你的代码就得暂停。一次两次没事,但如果你在循环里new一堆引用类型对象,GC频繁触发,程序就一卡一卡的。

我给你举个极端例子:

复制代码
// 这样写,每次循环都产生垃圾for (int i = 0; i < 1000000; i++){    object obj = new object(); // 引用类型,堆上分配}
// 换成值类型,压力小很多struct MyPoint { public int X; public int Y; }for (int i = 0; i < 1000000; i++){    MyPoint p = new MyPoint(); // 值类型,栈上分配,不触发GC}

划重点:

  • 高频创建的小对象,能用struct就用struct。

  • 但也别滥用,struct太大(超过16字节)反而不好,而且它是值传递,复制也有成本。

  • 追求性能的时候,心里要有根弦:引用类型多了,GC就忙了,GC一忙,用户就卡了。


最后,刚子想说

值类型和引用类型,说大不大,说小不小。面试背概念不难,难的是写代码的时候能自然而然地想到这些区别。

我刚入行那会儿也在这上面栽过跟头,改一个对象改了半天发现改的是副本,排查到半夜。后来慢慢才悟出来:概念不是用来背的,是用来救命的。

如果你觉得这篇文章帮到了你,点个赞,转给身边还在背概念、写代码还迷糊的兄弟。

我是刚子,一个还在写代码的.NET老程序员。咱们下回见!

精选推荐:C# 面试高频题:装箱和拆箱是如何影响性能的? - 码农刚子 - 博客园

相关推荐
步步为营DotNet7 小时前
深入剖析.NET 11 中 Microsoft.Extensions.AI 在 AI 驱动后端开发的进阶应用
人工智能·microsoft·.net
qq_454245037 小时前
GraphFoundation动态更新图
架构·c#·图论
愤豆7 小时前
07-Java语言核心-JVM原理-JVM对象模型详解
java·jvm·c#
张人玉8 小时前
上位机项目笔记
笔记·c#·上位机
小杍随笔9 小时前
【Rust Exercism 练习详解:Anagram + Space Age + Sublist(附完整代码与深度解读)】
开发语言·rust·c#
呆子也有梦10 小时前
redis 的延时双删、双重检查锁定在游戏服务端的使用(伪代码为C#)
redis·后端·游戏·缓存·c#
xyyaihxl11 小时前
C#数据库操作系列---SqlSugar完结篇
网络·数据库·c#
第二只羽毛12 小时前
C++ 高并发内存池2
大数据·开发语言·jvm·c++·c#
林鸿群13 小时前
竞彩网全栈项目实战:克隆与重构,从零构建 Vue3 + .NET 9 现代彩票网站
重构·.net