一:背景

1. 讲故事

最近在翻 netcore 源码看,发现框架中有不少的代码都被 ref 给修饰了,我去,这还是我认识的 ref 吗?就拿 Span 来说,代码如下:

public readonly ref struct Span<T>{public ref T GetPinnableReference(){ref T result = ref Unsafe.AsRef<T>(null);if (_length != 0){result = ref _pointer.Value;}return ref result;}public ref T this[int index]{get{return ref Unsafe.Add(ref _pointer.Value, index);}}             }

是不是到处都有 ref,在 struct 上有,在 local variable 也有,在 方法签名处 也有,在 方法调用处 也有,在 属性 上也有, 在 return处 也有,简直是应有尽有,太????????啦,那这一篇我们就来聊聊这个奇葩的 ref。

二:ref 各场景下的代码解析

1. 动机

不知道大家有没有发现,在 C# 7.0 之后,语言团队对性能这一块真的是前所未有的重视,还专门为此出了各种类和底层支持,比如说 Span, Memory,ValueTask,还有本篇要介绍的ref。

在大家传统的认知中 ref 是用在方法参数上,用于给 值类型 做引用传值,一个是为了大家业务上需要多次原地修改的情况,二个是为了避免值类型的copy引发的性能开销,不知道是哪一位大神脑洞大开,将 ref 应用在你所知道的代码各处,最终目的都是尽可能的提升性能。

2. ref struct 分析

从小就被教育 值类型分配在栈上,引用类型是在堆上,这话也是有问题的,因为值类型也可以分配在堆上,比如下面代码的 Location。

public class Program{public static void Main(string[] args){var person = new Person() { Name = "张三", Location = new Point() { X = 10, Y = 20 } };Console.ReadLine();}}public class Person{public string Name { get; set; }public Point Location { get; set; }  //分配在堆上}public struct Point{public int X { get; set; }public int Y { get; set; }}

其实这也是很多新手朋友学习值类型疑惑的地方,可以用 windbg 到托管堆找一下 Person 问问看,如下代码:


0:000> !dumpheap -type PersonAddress               MT     Size
0000010e368aadb8 00007ffaf50c2340       32     0:000> !do 0000010e368aadb8
Name:        ConsoleApp2.Person
MethodTable: 00007ffaf50c2340
EEClass:     00007ffaf50bc5e8
Size:        32(0x20) bytes
File:        E:\net5\ConsoleApp1\ConsoleApp2\bin\Debug\netcoreapp3.1\ConsoleApp2.dll
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007ffaf5081e18  4000001        8        System.String  0 instance 0000010e368aad98 <Name>k__BackingField
00007ffaf50c22b0  4000002       10    ConsoleApp2.Point  1 instance 0000010e368aadc8 <Location>k__BackingField0:000> dp 0000010e368aadc8
0000010e`368aadc8  00000014`0000000a 00000000`00000000

上面代码最后一行 00000014`0000000a 中的 14 和 a 就是 y 和 x 的值,稳稳当当的存放在堆中,如果你还不信就看看 gc 0代堆的范围。


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000010E368A1030
generation 1 starts at 0x0000010E368A1018
generation 2 starts at 0x0000010E368A1000
ephemeral segment allocation context: nonesegment             begin         allocated              size
0000010E368A0000  0000010E368A1000  0000010E368B55F8  0x145f8(83448)

从最后一行可看出,刚才的  0000010e368aadc8 确实是在 0 代堆 0x0000010E368A1030 - 0000010E368B55F8 的范围内。

接下来的问题就是能不能给 struct 做一个限制,就像泛型约束一样,不准 struct 分配在堆上,有没有办法呢?办法就是加一个 ref 限定即可,如下图:

从错误提示中可以看出,有意让 struct 分配到堆上的操作都是严格禁止的,要想过编译器只能将 class person 改成 ref struct person,也就是文章开头 Span  和  this[int index] 这样,动机可想而知,一切都是为了性能。

3. ref method 分析

给方法的参数传引用地址,我想很多朋友都已经轻车熟路了,比如下面这样:

public static int GetNum(ref int i){return i;}

现在大家可以试着跳出思维定势,既然可以往方法内仍 引用地址 ,那能不能往方法外抛 引用地址 呢?如果这也能实现就比较有意思了,我可以对集合内的某一些数据进行引用地址返回,在方法外照样可以修改这些返回值,毕竟传来传去都是引用地址,如下代码所示:

public class Program{public static void Main(string[] args){var nums = new int[3] { 10, 20, 30 };ref int num = ref GetNum(nums);num = 50;Console.WriteLine($"nums= {string.Join(",",nums)}");Console.ReadLine();}public static ref int GetNum(int[] nums){return ref nums[2];}}

可以看到,数组的最后一个值已经由 30 -> 50 了,有些朋友可能会比较惊讶,这到底是怎么玩的,不用想就是引用地址到处漂,不信的话,看看 IL 代码咯。


.method public hidebysig static int32& GetNums (int32[] nums) cil managed
{// Method begins at RVA 0x209c// Code size 13 (0xd).maxstack 2.locals init ([0] int32&)// {IL_0000: nop// return ref nums[2];IL_0001: ldarg.0IL_0002: ldc.i4.2IL_0003: ldelema [System.Runtime]System.Int32IL_0008: stloc.0// (no C# code)IL_0009: br.s IL_000bIL_000b: ldloc.0IL_000c: ret
} // end of method Program::GetNums.method public hidebysig static void Main (string[] args) cil managed
{IL_0013: ldloc.0IL_0014: call int32& ConsoleApp2.Program::GetNums(int32[])IL_0019: stloc.1IL_001a: ldloc.1IL_001b: ldc.i4.s 50IL_003e: popIL_003f: ret
} // end of method Program::Main

可以看到,到处都是 & 取值运算符,更直观一点的话用 windbg 看一下。


0:000> !clrstack -a
OS Thread Id: 0x7040 (0)
000000D4E777E760 00007FFAF1C5108F ConsoleApp2.Program.Main(System.String[]) [E:\net5\ConsoleApp1\ConsoleApp2\Program.cs @ 28]PARAMETERS:args (0x000000D4E777E7F0) = 0x00000218c9ae9e60LOCALS:0x000000D4E777E7C8 = 0x00000218c9aeadd80x000000D4E777E7C0 = 0x00000218c9aeadf00:000> dp 0x00000218c9aeadf0
00000218`c9aeadf0  00000000`00000032 00000000`00000000

上面代码处的 0x00000218c9aeadf0 就是 num 的引用地址,继续用 dp 看一下这个地址上的值为 16进制的32,也就是十进制的 50 哈。

三:总结

总的来说,netcore 就是在当初盛行的 云计算 和 虚拟化 时代诞生,基因和使命促使它必须要优化优化再优化,再小的蚂蚁也是肉,最后就是 C# 大法 ????????

C# 中的 ref 已经被放开,或许你已经不认识了相关推荐

  1. C#中关键字ref与out的区别(转)

    C#中关键字ref与out的区别(转) 在C#中,ref与out是很特殊的两个关键字.使用它们,可以使参数按照引用来传递. 总的来说,通常我们向方法中传递的是值.方法获得的是这些值的一个拷贝,然后使用 ...

  2. c#中使用ref和out传值

    c#中使用ref和out传值 首先,如果不使用这两个关键字,那是什么样 呢? 看下面的例子: 使用ref: using System; class Test { static void Swap(re ...

  3. Vue3 Composition API(二)——computed、watchEffect、setup中使用ref

    一.computed 在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理 在前面的Options API中,我们是使用computed选项来完成的: ...

  4. matlab中rowref的意思,Excel 中出现#REF是什么意思?(excel表格复制数据显示ref)

    EXCEL表格复制到另一表格的标签页中,全部显示为REF 应该是公式单元格出现了REF, 这是公式引用单元格缺失导致的.表格整体复制的话,如果表格中有引用到其他工作表中的内容,还是会让你作选择的. E ...

  5. Vue 中的 ref 属性详解

    Vue 中的 ref 属性详解 我们先来读一下vue的官方文档 我们来分析官方文档 首先ref的引用是相当于一个DOM节点(如果是子组件则指向的是其实例),而且是一个string类型的值. 通俗的将就 ...

  6. Vue中的ref是做什么的?

    Vue 中的 ref 属性详解_前端大猪草的博客-CSDN博客 Vue 中的 ref 属性详解 我们先来读一下vue的官方文档 我们来分析官方文档 首先ref的引用是相当于一个DOM节点(如果是子组件 ...

  7. 在iview中使用ref主要事项:

    在iview中使用ref主要事项: 在我开发中遇到一个需求:在2个表格嵌套的情况下按下回车键获取下一个input框的焦点 案例: ref的深入理解: //js document.getElements ...

  8. setup中使用ref

    目录 一.问题 二.解决方法 三.总结 一.问题 1.在选项式API中可以直接用 this.$refs.xxx来引用template中的DOM元素. 但是在组合式API setup中没有this,该如 ...

  9. vue中的ref属性

    vue中的ref属性   使用vue开发时经常会用到ref属性,ref属性有什么作用呢?我刚开始用的时候发现这个属性有点鸡肋,总是感觉可有可无,但是随着不断的使用vue,慢慢地发现它的作用还是挺重要的 ...

最新文章

  1. 面向对象的三个基本特征 和 五种设计原则
  2. 关于时间复杂度(持续更新.....)
  3. html图片平移,CSS3 按钮悬停时背景图片平移入场
  4. 神经网络是怎样理解图片的?谷歌大脑研究员详解特征可视化
  5. 云服务器部署项目:vue-cli 部署服务配置
  6. 3500个常用汉字列表
  7. 世界CEO薪酬排行榜第一位高达690 亿元
  8. Opencv+opencv_contrib安装
  9. Spring Cloud Alibaba 基础教程:支持的几种服务消费方式(RestTemplate、WebClient、Feign)
  10. js字符串分割split()
  11. ASPF与NAT ALG的工作原理与应用
  12. 产品原型设计规范——Axure
  13. 25.有5个人做在一起, 问第五个人多少岁? 他说比第四个人大2岁. 问第四个人岁数, 他说比第是三个人大2岁. 问第三个人, 又说比第二人大两岁. 问第二个人, 说比第一个人大两岁. 最后问第一个人
  14. rot13初学者和python的实现
  15. 油气蒸汽发生器行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  16. JSP入门教程(一)
  17. 英汉字典程序C语言,电子英汉字典_c语言版.doc
  18. php获取手机号码归属地
  19. 常用的各种消息下wParam及lParam值的含义
  20. .c文件生成.cgi文件

热门文章

  1. 子元素相对于父元素垂直居中对齐
  2. Django模板语言中的自定义方法filter过滤器实现web网页的瀑布流
  3. win7 32位 安装opencv-python后,运行时提示 from .cv2 import *: DLL load failed: 找不到指定的模块 的解决办法...
  4. html之file标签 --- 图片上传前预览 -- FileReader
  5. Windows服务二:测试新建的服务、调试Windows服务
  6. 部署webservice到远程服务器
  7. Facebook 如何管理150亿张照片
  8. q-dir 打不开文件_Q-Dir –多窗格文件管理器
  9. powerpoint预览_如何安排PowerPoint幻灯片的时间以进行更有效的演示
  10. Google的Project Stream准备在Chrome中播放AAA控制台游戏