c#引用(ref)的用法和应用实例

article/2025/9/28 7:28:50

无论是浅拷贝与深拷贝,C#都将源对象中的所有字段复制到新的对象中。不过,对于值类型字段,引用类型字段以及字符串类型字段的处理,两种拷贝方式存在一定的区别(见下表)。

1. 一般对C#中传值调用和传引用调用的理解

  • 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
  • 如果传递的参数是类(class)那么就是传引用调用。
  • 如果传递的参数前有ref或者out关键字,那么就是传引用调用。

验证示例的代码如下:

public class ArgsByRefOrValue{public static void Main(string[] args){// 实验1. 传值调用--基元类型int i = 10;Console.WriteLine("before call ChangeByInt: i = " + i.ToString());ChangeByInt(i);Console.WriteLine("after call ChangeByInt: i = " + i.ToString());Console.WriteLine("==============================================");// 实验2. 传值调用--结构体Person_val p_val = new Person_val();p_val.name = "old val name";Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);ChangeByStruct(p_val);Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);Console.WriteLine("==============================================");// 实验3. 传引用调用--类Person_ref p_ref = new Person_ref();p_ref.name = "old ref name";Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);ChangeByClass(p_ref);Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);Console.WriteLine("==============================================");// 实验4. 传引用调用--利用refPerson_ref p = new Person_ref();p.name = "old ref name";Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);ChangeByClassRef(ref p);Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);Console.ReadKey(true);}static void ChangeByInt(int i){i = i + 10;Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());}static void ChangeByStruct(Person_val p_val){p_val.name = "new val name";Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);}static void ChangeByClass(Person_ref p_ref){p_ref.name = "new ref name";Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);}static void ChangeByClassRef(ref Person_ref p){p.name = "new ref name";Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);}}public struct Person_val{public string name;}public class Person_ref{public string name;}

运行结果如下:

看起来似乎上面代码中实验3实验4是一样的,即对于类(class)来说,不管加不加ref或out,都是传引用调用。

其实,这只是表面的现象,只要稍微改一下代码,结果就不一样了。

修改上面代码,再增加两个实验。

 public class ArgsByRefOrValue{public static void Main(string[] args){// 实验1. 传值调用--基元类型int i = 10;Console.WriteLine("before call ChangeByInt: i = " + i.ToString());ChangeByInt(i);Console.WriteLine("after call ChangeByInt: i = " + i.ToString());Console.WriteLine("==============================================");// 实验2. 传值调用--结构体Person_val p_val = new Person_val();p_val.name = "old val name";Console.WriteLine("before call ChangeByStruct: p_val.name = " + p_val.name);ChangeByStruct(p_val);Console.WriteLine("after call ChangeByStruct: p_val.name = " + p_val.name);Console.WriteLine("==============================================");// 实验3. 传引用调用--类Person_ref p_ref = new Person_ref();p_ref.name = "old ref name";Console.WriteLine("before call ChangeByClass: p_ref.name = " + p_ref.name);ChangeByClass(p_ref);Console.WriteLine("after call ChangeByClass: p_ref.name = " + p_ref.name);Console.WriteLine("==============================================");// 实验4. 传引用调用--利用refPerson_ref p = new Person_ref();p.name = "old ref name";Console.WriteLine("before call ChangeByClassRef: p.name = " + p.name);ChangeByClassRef(ref p);Console.WriteLine("after call ChangeByClassRef: p.name = " + p.name);Console.WriteLine("==============================================");// 实验5. 传引用调用--类 在调用的函数重新new一个对象Person_ref p_ref_new = new Person_ref();p_ref_new.name = "old new ref name";Console.WriteLine("before call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);ChangeByClassNew(p_ref_new);    //注意区别这里Console.WriteLine("after call ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);Console.WriteLine("==============================================");// 实验6. 传引用调用--利用ref 在调用的函数重新new一个对象Person_ref p_new = new Person_ref();p_new.name = "old new ref name";Console.WriteLine("before call ChangeByClassRefNew: p_new.name = " + p_new.name);ChangeByClassRefNew(ref p_new);Console.WriteLine("after call ChangeByClassRefNew: p_new.name = " + p_new.name);Console.ReadKey(true);}static void ChangeByInt(int i){i = i + 10;Console.WriteLine("when calling ChangeByInt: i = " + i.ToString());}static void ChangeByStruct(Person_val p_val){p_val.name = "new val name";Console.WriteLine("when calling ChangeByStruct: p_val.name = " + p_val.name);}static void ChangeByClass(Person_ref p_ref){p_ref.name = "new ref name";Console.WriteLine("when calling ChangeByClass: p_ref.name = " + p_ref.name);}static void ChangeByClassRef(ref Person_ref p){p.name = "new ref name";Console.WriteLine("when calling ChangeByClassRef: p.name = " + p.name);}static void ChangeByClassNew(Person_ref p_ref_new){p_ref_new = new Person_ref();   //这样不改变 p_ref_newp_ref_newp_ref_new.name = "new ref name";Console.WriteLine("when calling ChangeByClassNew: p_ref_new.name = " + p_ref_new.name);}static void ChangeByClassRefNew(ref Person_ref p_new){p_new = new Person_ref();p_new.name = "new ref name";Console.WriteLine("when calling ChangeByClassRefNew: p_new.name = " + p_new.name);}}public struct Person_val{public string name;}public class Person_ref{public string name;}

则运行结果为:

实验5的运行结果似乎说明即使参数是类(class),只要不加ref,也是传值调用。

下面就引出了我的理解。

2. 没有ref时,即使参数为引用类型(class)时,也可算是一种传值调用

参数为引用类型时,传递的是该引用类型的地址的一份拷贝,“该引用类型的地址的一份拷贝”即为传值调用的“值”。

注意这里说传递的是该引用类型的地址的一份拷贝,而不是引用类型的地址。

下面将用图的形式来说明以上实验3,实验5和实验6中内存的情况。

2.1 首先是实验3

实验3的内存图如下,实参是函数ChangeByClass外的Person_ref对象,形参是函数ChangeByClass内的Person_ref对象。

捕获

从图中我们可以看出实参new出来之后就在托管堆上分配了内存,并且在栈上保存了对象的指针。

调用函数ChangeByClass后,由于没有ref参数,所以将栈上的实参p_val拷贝了一份作为形参,注意这里p_val(实参)p_val(形参)是指向托管堆上的同一地址。

所以说没有ref时,即使参数为引用类型(class)时,也可算是一种传值调用,这里的值就是托管堆中对象的地址(0x1000)。

调用函数ChangeByClass后,通过p_val(形参)修改了name属性的值,由于p_val(实参)p_val(形参)是指向托管堆上的同一地址,所以函数外的p_val(实参)的name属性也被修改了。

捕获

2.2 然后是实验5

上面的实验3从执行结果来看似乎是传引用调用,因为形参的改变导致了实参的改变。

下面的实验5就可以看出,p_val(形参)p_val(实参)并不是同一个变量,而是p_val(实参)的一个拷贝。

捕获

从图中可以看出第一步还是和实验3一样,但是在调用函数ChangeByClassNew后,就不一样了。

函数ChangeByClassNew中,对p_val(形参)重新分配了内存(new操作),使其指向了新的地址(0x1100),如下图:

捕获

所以p_val(形参)的name属性改了时候,p_val(实参)的name属性还是没变。

2.3 最后是实验6

我觉得实验6是真正的传引用调用。不废话了,直接上第一个图。

捕获

参数中加了ref关键字之后,其实传递的不是托管堆中对象的地址(0x1000),而是栈上p_val(实参)的地址(0x0001)。

所以这里实参和形参都是栈上的同一个东西,没有什么区别了。我觉得这才是真正的传引用调用。

然后调用了函数ChangeByClassRefNew,函数中对p_val(形参)重新分配了内存(new操作),使其指向了新的地址(0x1100)。

捕获

由于p_val(形参)就是p_val(实参),所以p_val(形参)的name属性改变后,函数ChangeByClassRefNew外的p_val(实参)的name属性也被改变了。

而原先分配的对象(地址0x1000)其实已经没有被引用了,随时会被GC回收。

3. 结论

  • 如果传递的参数是基元类型(int,float等)或结构体(struct),那么就是传值调用。
  • 如果传递的参数前有ref或者out关键字,那么就是传引用调用。
  • 如果传递的参数是类(class)并且没有ref或out关键字:
  1. 如果调用的函数中对参数重新进行了地址分配(new操作),那么执行结果类似传值调用
  2. 如果调用的函数中没有对参数重新进行了地址分配,直接就是使用了传递的参数,那么执行结果类似传引用调用

 


http://chatgpt.dhexx.cn/article/v94r2taN.shtml

相关文章

什么时候用ref,怎么用ref

文章目录 用refref是一个字符串ref是一个内联函数ref是一个回调函数ref通过调用React.createRef()生成 不用ref 用ref “打在我身,疼在她心”时,用ref。比如,点击事件绑定在某个标签上,事件处理函数中却要访问另一个完全不相关的…

[Vue]ref属性

前言 系列文章目录: [Vue]目录 老师的课件笔记,不含视频 https://www.aliyundrive.com/s/B8sDe5u56BU 笔记在线版: https://note.youdao.com/s/5vP46EPC 视频:尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 文章目录 前言1. ref…

什么是 ref 引用

1. 什么是 ref 引用 ref 用来辅助开发者在 不依赖于 jQuery 的情况下 ,获取 DOM 元素或组件的引用。 每个 vue 的组件实例上,都包含一个 $refs 对象 ,里面存储着对应的 DOM 元素或组件的引用。默认情况下, 组件的 $refs 指向一…

关于 Ref 你需要知道的知识点

Intro 在 React 项目中,有很多场景需要用到 Ref。例如使用 ref 属性获取 DOM 节点,获取 ClassComponent 对象实例;用 useRef Hook 创建一个 Ref 对象,以便解决像 setInterval 获取不到最新的 state 的问题;你也可以调用…

学习Vue3 第六章(认识Ref全家桶)

视频教程小满Vue3(第六章 Ref 全家桶 & 源码解析)_哔哩哔哩_bilibili ref 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。 案例 我们这样操作是无法改变message 的值 应为mess…

Vue中ref和$refs的介绍及使用

在JavaScript中需要通过document.querySelector("#demo")来获取dom节点,然后再获取这个节点的值。在Vue中,我们不用获取dom节点,元素绑定ref之后,直接通过this.$refs即可调用,这样可以减少获取dom节点的消耗…

C++实现身份证号码过滤与排序

1.描述 警察办案里检索到一批(n个)身份证号码,希望按出生日期对它们进行从大到小排序,如果有相同日期,则按身份证号码大小进行排序,如果是错误的身份证号,则从排序列表中删除(仅需判断前两位省级地区编码是否在下面的…

基于MATLAB的身份证号码识别系统

一、课题目标 本文主要介绍了一种采用基于matlab数字图像处理的图像识别技术,对身份证原始图像中的序列号标示进行图像识别的方法。该系统通过图像预处理、图像定位、图像校正并最终输出结果。在系统调试阶段,根据遇到的错误即时对原系统进行调整&#x…

python爬虫--根据身份证号码获取户籍地、出生年月等信息

一、背景 工作中偶尔会遇到这样的情况,给你一堆客户身份证号码,然后要你把对应的性别、生日、户籍地等信息弄出来。 最常用的方法就是用excel表套公式,这个方式如果用来取性别、生日这些信息的话问题不大,毕竟这些规则还好梳理&…

BP算法的身份证号码识别

摘 要:基于反向传播算法的多层前馈网络(简称BP神经网络)在图像处理方面应用较为广泛。目前,身份证号码识别技术 在图像识别领域迅猛发展,为提高识别身份证号码的速度及准确性,本文研究一种基于BP神经网络的身份证号码识别系统。首 先&#xf…

Matlab实现身份证号码快速识别

用Matlab 实现身份证号码快速识别 摘 要: 探讨身份证号码的快速识别。 首先从身份证图像中获取 0~9 共 10 个号码数字的样本图像, 从中提取其空间分布特征和结构特征; 再从待识别的身份证图像中提取各号码数字的空间分布特征和结构…

身份证号码的编码规则及校验

前言 本文内容适用于二代身份证,如无特殊说明,本文中所说的身份证皆指二代身份证。 笔者目前使用的jdk版本是1.6.0_29,Eclipse版本是Juno Release,Build id 20120614-1722。如无特殊说明,本文所有的Java代码都是基于…

基于Python实现身份证号码验证

基于Python实现身份证号码验证 摘要: 该设计主要使用python语言来实现身份证号验证程序。它运用 tkinter模块生成GUI界面。当用户输入身份证号,按下检查按钮,即可判断身份证号是否正确。该程序简洁,灵活,执行效率高。 关键词: 身…

java验证身份证号码的合格性

中国居民身份证校验码算法步骤如下: 将身份证号码前面的17位数分别乘以不同的系数。从第一位到第十七位的系数分别为: 7-9-10-5-8-4-2-1-6-3-7-9&…

Python+Opencv身份证号码区域提取及识别

前端时间智能信息处理实训,我选择的课题为身份证号码识别,对中华人民共和国公民身份证进行识别,提取并识别其中的身份证号码,将身份证号码识别为字符串的形式输出。现在实训结束了将代码发布出来供大家参考,识别的方式…

身份证号码案例

我国的居民身份证号码,由由十七位数字本体码和一位数字校验码组成。请定义方法判断用户输入的身份证号码是否合法,并在主方法中调用方法测试结果。规则为:号码为18位,不能以数字0开头,前17位只可以是数字,最…

Python之身份证号码的校验

该文章已同步收录到我的博客网站,欢迎浏览我的博客网站,xhang’s blog 问题描述: 中华人民共和国居民身份证号码由17 位数字和1位校验码组成。 其中,前6位为所在地编号,第7~14 位为出生年月日,第15~17位为登…

JAVA 身份证号码的验证

一、身份证结构和形式 在通用的身份证号码有15位的和18位的; 15位身份证号码各位的含义: 1、1-2位省、自治区、直辖市代码; 2、3-4位地级市、盟、自治州代码; 3、5-6位县、县级市、区代码; 4、7-12位出生年月日,比如670401代…

等价类划分测试—身份证

目录 0.题目要求: 注意:一个测试用例只能覆盖一个无效等价类,但可以覆盖尽可能多的前面未覆盖到的有效等价类。 1.划分等价类 1.1有效等价类 1.2无效等价类 2.测试用例: 0.题目要求: 针对国内18位身份证号码验证…

二维码文件分析

将二维码保存,进行解码 进行base64解码,网址:https://www.sojson.com/base64.html。第一次解码:6LZ5Liq5bCx5piv6aqM6KB56CB77yaQkozNVVCNVNZNg 第二次解码:这个就是验证码:BJ35UB5SY6 得到key