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 这一套玩法太另类了,按实际需求使用,不太会去考虑性能方面的问题。

相关推荐
yuniko-n1 分钟前
【JUC】四种内存屏障示例
java·1024程序员节
Shimeri12 分钟前
越卷越像:我用遗传算法做了个"内卷"实验
算法·three.js·数据可视化·遗传算法·进化算法
Evand J35 分钟前
【PMSM例程1】永磁同步电机(PMSM)转速与转子电角度观测算法|基于扩展卡尔曼滤波(EKF)的传感器状态估计与误差。附下载链接
算法·电机·1024程序员节·卡尔曼滤波
guo_wen_qiang35 分钟前
二叉树的层序遍历-使用双端队列Deque
算法·leetcode·1024程序员节
爱读书的小胖1 小时前
LeetCode 55 跳跃游戏 C 语言解题分析
c语言·数据结构·算法·leetcode·1024程序员节
CCblandy1 小时前
前缀缓存命中率不靠引擎靠排列:本机仿真 3 轮,易变字段放开头 98.0%→0.0%
人工智能·python·算法·自然语言处理·性能优化
上班摸鱼下班游戏1 小时前
Unity 仿生智能算法 从蚁群算法 开始
算法·unity·游戏引擎
pipixia200331 小时前
C++ 面试高频:namespace 的 6 个知识点,从 using 到匿名命名空间
c语言·学习·算法·嵌入式·命名空间·c++面试·匿名命名空间
念何架构之路1 小时前
Go reflect 包:结构体反射 —Field、Tag 与 VisibleFields
java·数据库·golang
liangbo72 小时前
03-JDK 10/11 局部变量推断与 LTS:var 到底该怎么用
java