值类型与引用类型:别再只背“栈和堆”了,看这 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# 面试高频题:装箱和拆箱是如何影响性能的? - 码农刚子 - 博客园

相关推荐
2601_949814692 小时前
如何使用C#与SQL Server数据库进行交互
数据库·c#·交互
CSharp精选营2 小时前
C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生
c#·try-catch·事务处理·transactionscope
加号33 小时前
C# 基于MD5实现密码加密功能,附源码
开发语言·c#·密码加密
weixin_520649873 小时前
C#闭包知识点详解
开发语言·c#
NQBJT5 小时前
[特殊字符] VS Code + Markdown 从入门到精通:写论文、技术文档的超实用指南
开发语言·vscode·c#·markdown
努力长头发的程序猿7 小时前
Unity2D当中的A*寻路算法
算法·unity·c#
xiaoshuaishuai817 小时前
C# Codex 脚本编写
java·服务器·数据库·c#
weixin_4474432520 小时前
AI启蒙Lean4
python·c#
我是唐青枫1 天前
C#.NET ValueTaskSource 深入解析:零分配异步、ManualResetValueTaskSourceCore 与使用边界
c#·.net
iCxhust1 天前
C#程序,窗体1向窗体2的textbox控件写入字符串“hello”
开发语言·c#