ref详解(C#)

本质上来说 ref 的就是把 C/C++ 指针的那一套又拿回来了,而且还封装成一套自己的玩法。

我想设计者的初心把 ref 的功能限制得死死的,可能也考虑到 C# 是一门面向业务开发的语言,讲究的是做项目快狠准,性能反而不是第一要素,这个时候的 ref 很简单,看一下代码:

csharp 复制代码
class Program
{
         static void Main(string[] args)
         {
             long price = 0;
              GetPrice(ref price);
              Console.WriteLine($"output: price={price}");
         }
          public static void GetPrice(ref long price)
         {
             price = 10;
         } 
    } 
// output: price=10

我相信大家都知道,方法参数中ref long price拿的是栈中的地址,对栈地址上的值进行修改,地址上的变量会被修改,和引用类型原理一致,接下来咋就从汇编的角度去看看。

csharp 复制代码
D:\net5\ConsoleApp4\ConsoleApp3\Program.cs @ 16:
 026b048e 8d4declea     ecx,[ebp-14h]
 026b0491 ff15a0ebc800    call    dword ptr ds:[0C8EBA0h] (ConsoleApp3.Program.GetPrice(Int64 ByRef), mdToken:
 06000002)
 026b0497 90              nop
 0:000> bp 026b0491
 0:000> g
 Breakpoint 1 hit
 ChangeEngineState
 eax=00000000 ebx=0057f354 ecx=0057f2d4 edx=783aaa50 esi=02979e7c edi=0057f2dc
 eip=026b0491 esp=0057f2c4 ebp=0057f2e8 iopl=0         nv up ei pl zr na pe nc
 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
026b0491 ff15a0ebc800    call    dword ptr ds:[0C8EBA0h] ds:002b:00c8eba0=00c2be10

从汇编的lea ecx,[ebp-14h]就能看到,将ebp-14这个单元的内存地址给了 ecx,这个 ecx 也就是作为参数传递给了Price方法,后续的赋值将会影响这个栈位置上的内容。

方法返回值上的 ref

这就有意思了,进入的时候传地址,回来的时候也想传地址,很显然方法线程栈上的 值类型 是传不出去的,毕竟方法返回后,esp,ebp 所控制的方法栈帧空间是要销毁的,所以只能是堆上对象才能实现。

为了方便理解,看如下代码:

csharp 复制代码
class Program
{
         static void Main(string[] args)
         {
              ref long price = ref TaskClass.GetCurrentPrice();
              price = 12;
               Console.WriteLine($"output: price={price}");
         }
          public static ref long GetCurrentPrice()
        {
            long[] nums = { 10, 20, 30 };
            return ref nums[1];
        }
    } 
// output: price=12

可以看到当前的price=12,同时nums这个数组也被修改了,可以用 windbg 验证一下。

csharp 复制代码
0:000> !dumpheap  -type System.Int64[]
   Address       MT     Size
 027ca7b0 04c39d00       36
       Statistics:
       MT    Count    TotalSize Class Name
 04c39d00        136 System.Int64[]
 Total 1 objects
 0:000> dq 027ca7b0 L4
 027ca7b0  00000003`04c39d00 00000000`0000000a
 027ca7c0  00000000`0000000c 00000000`0000001e

可以看到上面的000000000000000c被修改成price=12,这时候有人就不爽了,我不希望外面的代码能修改 price 内容,那怎么办呢? 还得在ref后面加上readonly,改造后如下:

到此时写法就有点疯狂了,对 C# 开发者来说很难理解,对熟悉 C/C++ 指针的朋友来说又很不习惯,太纠结了,下面是一段翻译过来的C/C++指针代码。

cpp 复制代码
const long long* getcurrentprice();

int main()
{
	int i = 0;

	const long long* price = getcurrentprice();

	price = 12;

	printf("num=%d, price=%d \n", i, *price);

}

const long long* getcurrentprice() {

	long long* num = new long long[3]{ 10,20,30 };
	return num + 1;
}

对 ref 变量的 in 操作

这又是一套 C/C++ 的玩法,有时候不希望某一个方法对 ref 变量进行修改,注意:是不希望某一个方法进行修改,其他方法是可以的,那这个怎么实现呢?这就需要在入参上加in前缀,把代码修改一下。

csharp 复制代码
class Program
{
    static void Main(string[] args)
    {
        ref long price = ref GetCurrentPrice();

        ModifyPrice(in price);

        Console.WriteLine($"output: price={price}");
    }

    public static ref long GetCurrentPrice()
    {
        long[] nums = { 10, 20, 30 };

        return ref nums[1];
    }

    public static void ModifyPrice(in long price)
    {
        price = 12;
        Console.WriteLine(price);
    }
}

可以看到,这时候报错了,如果换成 C++ 就很简单了,只需要在参数上把 in 改成 const 即可。

cpp 复制代码
void modifyprice(const long long* price) {
	*price = 12;
	printf("%d", *price);
}

总的来说,ref 这一套玩法太另类了,按实际需求使用,不太会去考虑性能方面的问题。

相关推荐
计算机安禾3 分钟前
【数据结构与算法】第42篇:并查集(Disjoint Set Union)
c语言·数据结构·c++·算法·链表·排序算法·深度优先
码界奇点3 分钟前
基于Spring Boot的前后端分离商城系统设计与实现
java·spring boot·后端·java-ee·毕业设计·源代码管理
吃着火锅x唱着歌5 分钟前
LeetCode 150.逆波兰表达式求值
linux·算法·leetcode
一叶飘零_sweeeet5 分钟前
深度剖析:Java 并发三大量难题 —— 死锁、活锁、饥饿全解
java·死锁·活锁·饥饿
IT乐手11 分钟前
java 对比分析对象是否有变化
android·java
云烟成雨TD15 分钟前
Spring AI Alibaba 1.x 系列【18】Hook 接口和四大抽象类
java·人工智能·spring
Hachi被抢先注册了23 分钟前
Docker学习记录
java·云原生·eureka
YuanDaima204827 分钟前
二分查找基础原理与题目说明
开发语言·数据结构·人工智能·笔记·python·算法
阿Y加油吧33 分钟前
两道中等 DP 题拆解:打家劫舍 & 完全平方数
算法·leetcode·动态规划
七颗糖很甜36 分钟前
python实现全国雷达拼图数据的SCIT风暴识别
python·算法·scipy