C#|.net core 基础 - 值传递 vs 引用传递
不知道你在开发过程中有没有遇到过这样的困惑:这个变量怎么值被改?这个值怎么没变?
今天就来和大家分享可能导致这个问题的根本原因值传递 vs 引用传递。
在此之前我们先回顾两组基本概念:
值类型** vs 引用类型**
**值类型:**直接存储数据,数据存储在栈上;
**引用类型:**存储数据对象的引用,数据实际存储在堆上。
形参 vs 实参
**形参:**即形式参数,表示调用方法时,方法需要你传递的值。方法声明定义了其形参。也就是说在定义方法时,紧跟在方法名后面括号中的参数列表就是形参。
**实参:**即实际参数,表示调用方法时,你传递给方法形参的值。调用代码在调用过程时提供实参。也就是说在调用方法时,紧跟在方法名后面括号中的参数列表就是实参。
再来回顾一下值类型和引用类型在内存中是怎么存储的呢?
对于值类型变量的值直接存储在栈中,如下图的int a=10,10就直接存在栈空间中,而其栈空间对应的内存地址为0x66666668;对于引用类型变量本身存储的是实例对象的引用,即实例对象在堆中的实际内存地址,因此引用类型变量是存储其实例对象的引用于栈上,如下图中变量Test a在栈中实际存储的是实例对象Test a在堆中的内存地址0x88888880,而栈空间对应的内存地址为0x66666668。

栈也是有内存地址的,这一点很重要,无论栈空间上存储的是值还是引用地址,这个栈空间本身也有自己对应的内存地址。
什么是值传递?什么是引用传递?
值传递:如果变量按值传递给方法,则会把变量的副本传递给方法。对于值类型则把变量的副本传递给方法,对于引用类型则把变量的引用的副本传递给方法。因此被调用方法参数会创建一个新的内存地址用于接收存储变量,因此在方法内部对变量修改并不会影响原来的值。
引用传递:如果变量按引用传递给方法,则会把变量的引用传递给方法,对于值类型则把变量的栈空间地址传递给方法,对于引用类型则把变量的引用的栈空间地址传递给方法。因此被调用方法参数不会创建一个新的内存地址用于接收存储变量,意味着形参与实参共同指向相同的内存地址,因此在方法内部修对变量修改会影响原来的值。
上面的描述可能有点拗口,下面我们在基于值类型、引用类型、值传递、引用传递各种组合进行一个详细说明。
01值类型按值传递
当值类型按值传递时,调用者会把值类型变量的副本传递给方法,因此被调用方法参数会创建一个新的内存地址用于接收存储变量,因此当在方法内部对参数进行修改时并不会影响调用者调用处的值类型变量。
传递值类型变量的副本就是相当于在栈上,又复制了一个同样的值,而且内存地址还不一样,所以互不影响。如下图把a赋值给b,则b直接新开辟了一个栈空间,虽然a和b都是10,但是它们在不同的地址空间中,因此如果他们各自被修改了,也互不影响。

下面我们写个例子演示一下,这个例子就是定义个变量a并赋值,然后调用一个方法此方法内对传进来的参数a进行加1,具体代码如下:
public static void ValueByValueRun()
{var a = 10;Console.WriteLine($"调用者-调用方法前 a 值:{a}");ChangeValueByValue(a);Console.WriteLine($"调用者-调用方法后 a 值:{a}");
}
public static void ChangeValueByValue(int a)
{Console.WriteLine($" 被调用方法-接收到 a 值:{a}");a = a + 1;Console.WriteLine($" 被调用方法-修改后 a 值:{a}");
}
运行结果如下:

通过代码执行结果可以发现,方法内对变量的修改已经生效,但是不没有影响到调用者调用处的变量值。
02引用类型按值传递
当引用类型按值传递时,调用者会把引用类型变量的引用副本传递给方法,因此被调用方法参数会创建一个新的内存地址用于接收存储变量,而对于一个引用类型变量来说其本身存储的就是引用类型实例对象的引用副本,而方法接收到的也是此变量引用的副本,所以调用者参数和被调用方法参数是引用了同一个实例对象的两个引用副本。如下图Test a可以理解为调用者传的实参,Test b可以理解为被调用方法定义的形参,这两个参数都只是指向堆中Test a的引用副本。

因此可以得出两个结论:
1、变量a和b都是指向实例对象Test a的引用,所以无论变量a或b,只要有一个更新了实例成员则另一个变量也会同步发生变化。
2、虽然变量a和b都是指向实例对象Test a的引用,但是他们存储在栈上的内存地址却不同,因此如果他们各种重新分配实例也就是new一个新对象,则另一个变量无法感知到还是保持原因状态不变。
我们先用代码说明第一个结论:
public static void ChangeReferenceByValueRun()
{var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");ChangeReferenceByValue(a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void ChangeReferenceByValue(Test a)
{Console.WriteLine($" 被调用方法-接收到 a.Age 值:{a.Age}");a.Age = a.Age + 1;Console.WriteLine($" 被调用方法-修改后 a.Age 值:{a.Age}");
}
运行结果如下:

可以看到被调用方法中a实例对象的Age属性发生变化后,调用者中变量也同步发生了变化。
对于第二个结论我们这样论证,在方法中直接对参数new一个新对象,看看原变量是否发生变化,代码如下:
public static void NewReferenceByValueRun()
{var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");NewReferenceByValue(a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void NewReferenceByValue(Test a)
{Console.WriteLine($" 被调用方法-接收到 a.Age 值:{a.Age}");a = new Test{Age = 100};Console.WriteLine($" 被调用方法-new后 a.Age 值:{a.Age}");
}
执行结果如下:

可以发现当在方法中对变量执行new操作后,调用者处的变量并没有发生变化。
为什么会这样呢?因为对于引用类型来说,形参和实参是对引用类型的实例对象引用的两个副本,而这两个副本存储在栈上又分别在不同的内存地址空间上,而new主要就是重新分配内存,这就导致形参变量a=new后,栈上形参变量a指向了Test新的实例对象的引用,而实参变量a还是保持原有实例对象引用不变。
如下图所示。

03值类型按引用传递
当值类型按引用传递时,调用者会把值类型变量对应的栈空间地址传递给方法,因此被调用方法参数不会创建一个新的内存地址用于接收存储变量,因此当在方法内部对参数进行修改时并同样会影响调用者调用处的值类型变量。

传递值类型变量对应的栈空间地址就意味着形参与实参共同指向相同的内存地址,所以才导致对形参修改时,实参也会同步发生变化。
我们用一个小例子演示一下:
public static void ValueByReferenceRun()
{Console.WriteLine($"值类型按引用传递");var a = 10;Console.WriteLine($"调用者-调用方法前 a 值:{a}");ChangeValueByReference(ref a);Console.WriteLine($"调用者-调用方法后 a 值:{a}");
}
public static void ChangeValueByReference(ref int a)
{Console.WriteLine($" 被调用方法-接收到 a 值:{a}");a = a + 1;Console.WriteLine($" 被调用方法-修改后 a 值:{a}");
}
执行结果如下:

可以发现调用者处的值类型变量已经发生改变。
04引用类型按引用传递
当引用类型按引用传递时,调用者会把引用类型变量对应的栈空间地址传递给方法,因此被调用方法参数不会创建一个新的内存地址用于接收存储变量,因此当在方法内部对参数进行修改时并同样会影响调用者调用处的引用类型变量。

传递引用类型变量对应的栈空间地址就意味着形参与实参共同指向相同的内存地址,因此对形参修改时,实参也会同步发生变化,而且这个里的修改不单单指修改实例成员,还包括new一个新实例对象。
下面我们看一个修改实例成员的例子:
public static void ChangeReferenceByReferenceRun()
{Console.WriteLine($"引用类型按引用传递 - 修改实例成员");var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");ChangeReferenceByReference(ref a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void ChangeReferenceByReference(ref Test a)
{Console.WriteLine($" 被调用方法-接收到 a.Age 值:{a.Age}");a.Age = a.Age + 1;Console.WriteLine($" 被调用方法-修改后 a.Age 值:{a.Age}");
}
执行结果如下:

再看看new一个新对象的例子:
public static void NewReferenceByReferenceRun()
{Console.WriteLine($"引用类型按引用传递 - new 新实例");var a = new Test{Age = 10};Console.WriteLine($"调用者-调用方法前 a.Age 值:{a.Age}");NewReferenceByReference(ref a);Console.WriteLine($"调用者-调用方法后 a.Age 值:{a.Age}");
}
public static void NewReferenceByReference(ref Test a)
{Console.WriteLine($" 被调用方法-接收到 a.Age 值:{a.Age}");a = new Test{Age = 100};Console.WriteLine($" 被调用方法-new后 a.Age 值:{a.Age}");
}
执行结果如下:

另外string是一个特殊的引用类型,string类型变量的按值传递和按引用传递和值类型是一致的,也就是要把string类型当值类型一样看待就行。string类型的特殊性我们后面会单独具体介绍。
在C#中以下修饰符可应用与参数声明,并且会使得参数按引用传递:ref、out、readonly ref、in。对于每个修饰符具体怎么使用就不再这里细说了。
相信到这里你应该就可以回答我之前在《LeetCode题集-2 - 两数相加》最后提的问题了。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
相关文章:
C#|.net core 基础 - 值传递 vs 引用传递
不知道你在开发过程中有没有遇到过这样的困惑:这个变量怎么值被改?这个值怎么没变? 今天就来和大家分享可能导致这个问题的根本原因值传递 vs 引用传递。 在此之前我们先回顾两组基本概念: 值类型** vs 引用类型** **值类型&a…...
axure的下载,激活,汉化全过程,多图
1.前言 下载地址:https://pan.baidu.com/s/12xo1mJer2hmBK7QrYM5v-Q?pwd0107#list/path%2Fcsdn%E5%85%B1%E4%BA%AB%E6%96%87%E4%BB%B6 源文章:https://blog.csdn.net/iwanttostudyc/article/details/123773796?ops_request_misc%257B%2522request%25…...
LCR 026
题目:LCR 026 解法一:线性表 将链表中所有元素加入数组中,创建两个指针,分别指向数组的头部和尾部,然后向中间遍历 public void reorderList(ListNode head) {if (head null || head.next null || head.next.next …...
万能小程序运营管理系统 _requestPost 任意文件读取漏洞复现
0x01 产品简介 万能小程序运营管理系统是一种功能全面的系统,旨在帮助开发者和运营人员更好地管理和推广小程序。该系统集成了多种功能模块,覆盖了从小程序开发、部署到运营管理的全链条服务。系统通过提供丰富的功能和工具,帮助用户轻松搭建、管理和优化小程序。该系统支持…...
libyuv之linux编译
文章目录 一、下载源码二、编译源码三、注意事项1、银河麒麟系统(aarch64)(1)解决 armv8-adotprodi8mm 指令集支持问题(2)解决 armv9-asve2 指令集支持问题 一、下载源码 到GitHub网站下载https://github.…...
vue3路由基本使用
在 Vue 3 中,路由指的是应用程序的导航系统,允许你在不同的视图或页面之间进行切换。通过 vue-router 插件,你可以定义路由规则,将 URL 路径映射到 Vue 组件,实现页面间的跳转和状态管理。使用路由,用户可以…...
哪些人适合学习人工智能?
人工智能(AI)的浪潮正席卷全球,它不仅是科技领域的一场革命,更是社会进步的重要推手。随着AI技术的不断成熟和应用领域的不断拓展,越来越多的人开始关注并渴望掌握这一前沿技术。那么,究竟哪些人适合学习人…...
计算机的错误计算(九十七)
摘要 讨论 的计算精度问题。 由计算机的错误计算(九十六)知,IEEE754-2019标准中含有 运算。 另外,似乎没有语言直接编程实现内置了该运算。 例1. 已知 x-0.9999999999076 . 计算 不妨用 Python的 math库与 numpy库中的 …...
Flask-Migrate的使用
组织一个 Flask 项目通常需要遵循一定的结构,以便代码清晰、可维护。下面是一个典型的 Flask 项目结构: my_flask_app/ │ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── forms.py │ ├── templat…...
python怎么输入整数
使用input()函数输入一个整数: ainput(“请输入一个整数\n”) 结果却报错TypeError: str object cannot be interpreted as an integer 查阅文档发现input()函数返回值是str型。 我们只需要这样转换: aint(input("请输入一…...
代码随想录打卡Day36
今天做的头皮发麻,除了第一道题是自己AC的,剩下两道题目都是看视频才AC的,主要是看了视频也花了很久时间才想清楚。 1049. 最后一块石头的重量 II 这道题一开始没什么思路,但是看到提示说和昨天的分割子集很像,然后我…...
速盾:凡科建站开cdn了吗?
凡科建站是一家专业的建站平台,提供了多种功能和工具来帮助用户快速搭建自己的网站。随着互联网技术的不断发展,网站的访问速度和稳定性成为了越来越重要的考虑因素。为了优化用户体验,提高网站的加载速度,凡科建站已经开启了CDN&…...
python贪吃蛇游戏项目源码【免费】
使用Pygame库实现的贪吃蛇游戏。Pygame是一个用于创建视频游戏的Python模块集合,它提供了图形和声音库,使游戏开发变得容易。 初始化设置 屏幕大小 (SCREEN_WIDTH, SCREEN_HEIGHT): 定义了游戏窗口的宽度和高度。方格大小 (SIZE): 定义了游戏中每个小方…...
Mycat搭建分库分表
分库分表解决的问题 单表数据量过大带来的性能和存储容量的限制的问题: 索引效率下降读写瓶颈存储容量限制事务性能问题分库分表架构 再搭建一对主从复制节点,3307主节点,3309从节点配置数据源 dw1 , dr1,创建集群c1创建逻辑库 CREATE DATAB…...
Python中的数据结构
1.列表 可以将列表理解为一个购物清单,这个清单里面的的每个元素可以是任何类型,并且可以重复(这里区别了列表与数组)。列表用“[]”表示。 #这里定义了一个列表,列表中的元素的类型不同。 lsit_a [a, b, c, 1, 2] …...
mysql笔记8(多表查询)
文章目录 1. union联合查询可能会用到去重操作 2. inner join 内连接3. left join 左连接4. right join 右连接5. cross join 交叉连接6. natural join 自然连接natural left join 自然左连接natural right join 自然右连接自然连接的两张表没有同名字段怎么办? 7. …...
typescript-tsconfig文件解释
ts.config.json 作用 用于标识 TypeScript 项目的根路径用于配置 TypeScript 编译器用于指定编译的文件 重要字段 files - 设置要编译的文件的名称;include - 设置需要进行编译的文件,支持路径模式匹配;exclude - 设置无需进行编译的文件…...
所有用贪心的算法和所有用动态规划(dp)的算法合集
贪心 纯贪心算法(常在普及组中做暴力题正解使用)DijkstraBFS(最短路径)KruskalPrimSollin(Boruvka)二分三分最值排序LCA(纯贪心版) 动态规划(dp) Floyd线性DP区间DP背…...
论文阅读 | 基于流模型和可逆噪声层的鲁棒水印框架(AAAI 2023)
Flow-based Robust Watermarking with Invertible Noise Layer for Black-box DistortionsAAAI, 2023,新加坡国立大学&中国科学技术大学本论文提出一种基于流的鲁棒数字水印框架,该框架采用了可逆噪声层来抵御黑盒失真。 一、问题 基于深度神经网络…...
上线跨境电商商城的步骤
上线一个跨境电商商城涉及多个步骤,从前期准备到上线后的维护。以下是一些关键步骤: 1. 市场调研与规划 目标市场分析:研究目标市场的需求、竞争对手和消费者行为。法律法规:了解并遵守目标市场的法律法规,包括税收、…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
