一直以为对于引用类型做为参数在方法调用时加不加 ref 关键字是没有区别的。但是今天一调试踪了一下变量内存情况才发现大有不同。

直接上代码,结论是:以下代码是使用了 ref 关键字的版本。它输出10。如果不使用ref 关键字则输出 1,2,3

 1    class Program
2 {
3 static void Main(string[] args)
4 {
5 int[] myArray = new int[] { 1, 2, 3 };
6 new SetClass().SetArray(ref myArray);
7 /*
8 不加ref关键字的引用类型传参情况 加上ref关键字后的引用类型传参情况
9 &myArray &myArray
10 0x000000c088d7e3d0 0x0000008151b7e6b0
11 *&myArray: 0x0000029b3f84ae00 *&myArray: 0x000001c5e5a7ae00
12 */
13
14 foreach (int i in myArray)
15 Console.WriteLine(i);
16 /*
17 &myArray &myArray
18 0x000000c088d7e3d0 0x0000008151b7e6b0
19 *&myArray: 0x0000029b3f84ae00 *&myArray: 0x000001c5e5a7ae00
20 */
21 }
22
23 }
24
25 class SetClass
26 {
27 //如果形参 array 是引用类型时(不论加不加 ref 关键字),则在方法执行时方法体内的局部变量 array 指向外部传进来的实参所指向的内存空间。
28 //但是加上 ref 关键后在方法执行时方法体内接收传进来的实参时,并不会给 array 变量分配内存空间,即 变量array就是变量myArray。
29 internal void SetArray(ref int[] array)
30 {
31 /*
32 &array &myArray
33 0x000000c088d7e388 0x0000008151b7e6b0
34 *&array: 0x0000029b3f84ae00 *&myArray: 0x000001c5e5a7ae00
35 */
36 array = new int[] { 10 };
37 /*
38 &array &myArray
39 0x000000c088d7e388 0x0000008151b7e6b0
40 *&array: 0x0000029b3f84bbf0 *&myArray: 0x000001c5e5a7ae00
41 */
42 }
43 }
44 }

一些说明:

  1. 以上代码中的注释可纵向分隔为两部分来看,左边部分是不加ref关键字调试时查看的内存情况,右边则是加上ref关键字后的情况。
  2. 每个/* */中注释都是代码执行完注释所在位置的上一语句后的内存情况。
  • &myArray                         //表示获取这个变量内存的指令

0x000000c088d7e3d0            //表示这个变量在内存中的地址

                *&myArray: 0x0000029b3f84ae00  //表示这个变量指向的内存空间的对象的地址

  • 在visual studio 2019 中查看变量内存地址的方法:

方法一:

        在即时窗口输入取地址符+变量名如 &a 这是会输出如下 两行:

        0x000000325637e570

        *&a: 0x00000209ba0dad58

        第一行 0x000000325637e570 代表变量本身的内存地址,第二行 *&a: 0x00000209ba0dad58 表示变量指向的对象的内存地址

方法二:

        【调试】-【窗口】-【内存】-从列出来的4个中选一个,然后会调出内存查看窗口。在内存查看地窗口中的【地址】里输入[取地址符]+[变量名]如 &a ,这时地址中的&a会变成变量的十进制表示的内存地址,如:0x000000325637E570

补充几张调试中断在不同语句时的一些内存情况截图:(加上ref关键字后的引用类型传参情况图)

1.

2.

3.

4.

2022-07-31再次总结:

我们知道,不论值类型还是引用类型,内存存储单元中的数据是依靠存储单元地址来访问的。

对于值类型数据的内存模型就是直接把值放在内存单元里,需要访问值时直接用内存地址就能获取这个地址中存储的数据了。这个模型直观而简单很好理解。

而引用类型的内存存储模型是由栈内存+堆内存的结构共同实现的。具体细节就是:引用类型变量的数据内容(命名为content)放在堆内存(我们给这个堆内存地址一个名字叫H),然后还需要有一个栈内存(再把栈内存地址命名为S),这个地址为S的栈内存里存放的值就是H,是的 就是堆内存的地址,这样就要访问content就需要先访问S,得到S中的内容才得到了地址H,最后才能访问到H地址里的内容content。基于S中存放的值是另一个内存地址而不是数据内容本身的原因,所以人们常把S及其值叫做指针(引用类型数据使用的正是这种间接访问数据的设计模型)。

接下来是C#语言对于引用类型方法传递参数的设计及实现。

先不考虑ref关键字,对于方法的引用类型参数,其在方法接收外部变量时的接收细节是这样:方法内部会创建一个新的栈内存也就是个指针,其内存单元就是用来接收那个外部传进来的变量所在的堆内存的地址,采用这样的方式来实现对外部变量的接收也就是说本质上是传递堆内存地址。但是注意,外部变量原来那个栈内存指针也指向同样的堆内存。即在方法内部和外部这两个指针都指向同一块堆内存但这两个指针各是各,是不同的栈内存地址。基于这种设计,我们可以看出,在前面的示例中,如果不使用ref关键字,则在SetArray方法内部 array=new int[]...这行指令实际上是先按new int[]指令创建了一个新的堆内存(放新的数组),然后把指针array的存储的值(赋值前它是原堆内存地址)更新为新的堆内存的地址,那么原堆内存地址在方法内部也就无法再访问了,而且这个地址值更新的进程也与方法外部的指针myArray无关,即方法外部的myArray依然指向它原先那个堆内存地址。

最后,我们来考虑ref关键字。一个方法的引用类型参数使用 ref 关键字后会使得在方法在接收外部变量时改变默认传递参数的行为。具体表现就是加上ref后,在方法内部不再在栈内存上创建一个新的指针用来接收外部变量其堆内存的地址,而是直接使用外部变量的指针,等于把外部变量的指针本身给传进来了。

关键归纳:不加ref传外部变量堆内存地址;加上ref传外部变量的栈内存地址即指针地址本身。

我觉得这次总结的还不错,希望对您有所帮助。也希望自己不要再忘记这些关键的知识点了。

C#中引用类型的变量做为参数在方法调用时加不加 ref 关键字的不同之处的更多相关文章

  1. Linux中PATH环境变量的作用和使用方法

    关于PATH的作用:PATH说简单点就是一个字符串变量,当输入命令的时候LINUX会去查找PATH里面记录的路径.比如在根目录/下可以输入命令ls,在/usr目录下也可以输入ls,但其实ls这个命令根 ...

  2. Eclipse中输入系统变量和运行参数

    在开发时,有时候可能需要根据不同的环境设置不同的系统参数,我们都知道,在使用java -jar命令时可以使用-D参数来设置运行时的系统变量,同样,在Eclipse中运行java程序时,我们怎么设置该系 ...

  3. Eclipse中输入系统变量和运行参数--转

    原文地址:http://chenzhou123520.iteye.com/blog/1931670 在开发时,有时候可能需要根据不同的环境设置不同的系统参数,我们都知道,在使用java -jar命令时 ...

  4. 【C#小知识】C#中一些易混淆概念总结---------数据类型存储,方法调用,out和ref参数的使用

    这几天一直在复习C#基础知识,过程中也发现了自己以前理解不清楚和混淆的概念.现在给大家分享出来我的笔记: 一,.NET平台的重要组成部分都是有哪些 1)FCL (所谓的.NET框架类库) 这些类是微软 ...

  5. vue中prop传值时加不加v-bind(冒号:)

    前言:有关Vue中父组件通过prop传值给子组件时,是否加v-bind的问题,没弄清楚时感觉很乱,弄清楚之后很简单. 由于结果记起来很容易,所以先给出结果: 只有传递字符串常量时,不采用v-bind形 ...

  6. Struts2中Action对象的set方法和get方法调用规则

    Struts的Action是采用的是多实例多线程设计,而不是像Servlet那样采用单实例多线程设计,因此在struts中,一个请求就对应一个Action对象,个对象之间的数据相互之间互不干扰.没接到 ...

  7. python 传入任意多个参数(方法调用可传参或不传参)

    1.可传参数与不传参数,在定义中给参数设置默认值 class HandleYmal: """ 获取测试环境的配置 """ def __ini ...

  8. 反射的妙用-类名方法名做参数进行方法调用实例demo

    首先声明一点,大家都会说反射的效率低下,但是大多数的框架能少了反射吗?当反射能为我们带来代码上的方便就可以用,如有不当之处还望大家指出 1,项目结构图如下所示:一个ClassLb类库项目,一个为测试用 ...

  9. 驱动中获取PsActiveProcessHead变量地址的五种方法也可以获取KdpDebuggerDataListHead

    PsActiveProcessHead的定义: 在windows系统中,所有的活动进程都是连在一起的,构成一个双链表,表头是全局变量PsActiveProcessHead,当一个进程被创建时,其Act ...

随机推荐

  1. 859. Buddy Strings - LeetCode

    Question 859. Buddy Strings Solution 题目大意: 两个字符串,其中一个字符串任意两个字符互换后与另一个字符串相等,只能互换一次 思路: diff 记录不同字符数 两 ...

  2. JavaScript正则中//g, g 的作用

    //正则表达式的标准写法regexp = new RegExp(pattern[, flag]); pattern:  模板的用法是关键,也是本章的主要内容.    flag:     "i ...

  3. 差分优化建边(Tax)

    [Luogu P6822PA2012]Tax] (http://www.luogu.com.cn/problem/P6822") All right. Let's go! 题目描述 给出一个 ...

  4. 10分钟学会 API 测试 !

    本文面向对象主要是后端开发人员   API 开发好之后,我们需要对 API 进行简单的调试,确保 API 可以跑通再提交给前端人员进行对接或者是测试人员对 API 进行测试:   在测试过程中我们关注 ...

  5. 可靠的分布式KV存储产品-ETCD-初见

    目录 Paxos Raft(Understandable Distributed Consensus) 名词介绍 Leader Election Log Replication 请求完整流程 etcd ...

  6. python 的 @staticmethod和@classmethod和普通实例方法

    参考:https://www.huaweicloud.com/articles/12607084.html https://blog.csdn.net/qq_30708445/article/deta ...

  7. 《原CSharp》第二回 巧习得元素分类 子不知怀璧其罪

    第二回 巧习得元素分类 子不知怀璧其罪 ===================================================================== 云溪父亲见状看了看云 ...

  8. 25.MYsql数据库管理

    MYsql数据库管理 目录 MYsql数据库管理 数据库基本操作 库和表 常用的数据类型 查看数据表结构 查看当前服务器的数据库 查看数据库中包含的表 查看表的结构 SQL语句 创建及删除数据库和表 ...

  9. Maven的安装 和idea的配置

    Maven的安装 和idea的配置 工欲善其事 必先利其器 1 下载maven 官网 下滑 找到Files tar.gz 是linux系统的 .zip window系统 2 maven安装和配置到环境 ...

  10. Freeswitch使用originate转dialplan

    概述 Freeswitch是一款非常好用的开源VOIP软交换平台. 最近在对fs做一些功能测试,测试的过程中产生的一个需求,如何从fs发起呼叫并把后续的呼叫流程转到某一个dialplan上,这样在测试 ...