​

一直以为对于引用类型做为参数在方法调用时加不加 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. Python技法:用argparse模块解析命令行选项

    1. 用argparse模块解析命令行选项 我们在上一篇博客<Linux:可执行程序的Shell传参格式规范>中介绍了Linux系统Shell命令行下可执行程序应该遵守的传参规范(包括了各 ...

  2. python使用vosk进行中文语音识别

    操作系统:Windows10 Python版本:3.9.2 vosk是一个离线开源语音识别工具,它可以识别16种语言,包括中文. 这里记录下使用vosk进行中文识别的过程,以便后续查阅. vosk地址 ...

  3. 520表白小程序设计Python代码详解(PyQt5界面,B站动漫风)

    摘要:介绍一个动漫风的表白小程序,界面使用Python以及PyQt实现,界面样式经过多次美化调整,使得整体清新美观.本文详细介绍代码设计和实现过程,不仅是居家表白必备,而且适合新入门的朋友学习界面设计 ...

  4. [2018-03-04] 利用 Settings Sync 插件同步 VS Code 设置

    VS Code 已原生支持设置同步,本文仅备份记录 [2018-03-04] 早就听说这个插件了,今天用了一下,确实挺方便的.通过把配置文件创建为 Gist 上来实现了 VS Code 设置的同步,下 ...

  5. Spring Ioc源码分析系列--Bean实例化过程(二)

    Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...

  6. Dubbo3 源码系列 -- 环境准备

    Dubbo3 源码系列 -- 环境准备 前言 工作中一直使用Dubbo项目,借着这次机会通过源码的方式来学习下Dubbo的源码内容.目前市面上很多都是的Dubbo2系列的教程:就连目前的Dubbo的官 ...

  7. React简单教程-5-使用mock

    前言 一个前后端分离的项目,前端人员需要对接后端的接口.如果在后端的接口没有开发好,或者没有测试版可以对接的情况下,前端人员也不能坐等后端接口写好后再开始开发. 一个项目的,理想情况下接口的规范应该是 ...

  8. mybatis中返回一个List字段

    目的:在一个查询接口里面,返回一个人的信息,以及这个人所携带的东西的信息,返回效果如下: { "msg": { "listMain": [ { "id ...

  9. 【Java面试】请说一下Mysql索引的优点和缺点?

    今天分享的这道面试题,让一个工作4年的小伙子去大众点评拿了60W年薪. 这道面试题是: "请你说一下Mysql索引的优点和缺点" 关于这道题,看看普通人和高手的回答 普通人: 嗯. ...

  10. 入坑KeePass(三)安全设置完后后留存

    1.文件> 数据库设置 > 安全 迭代次数改成500000 2.工具 > 选项 2.1.安全 2.2.策略 2.3.集成 2.4高级