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

相关推荐
wuminyu3 分钟前
Kafka配置TLS/SSL加密传输时零拷贝失效分析
java·linux·c语言·jvm·c++
边境悍匪8 分钟前
蜗牛学苑 Java 智能体学习 Day41|ElementPlus 布局、Vue‑Router 登录、Axios 思维导图复盘
java·vue.js·学习
心中有你021414 分钟前
Java Swing实现校园最短路径导航系统(Dijkstra算法完整源码+详细解析)
java·开发语言·算法
Alphapeople17 分钟前
路径规划算法的python实现
开发语言·python·算法
重生之小比特18 分钟前
【Java SE】数组的定义与使用
java·开发语言
LabVIEW开发20 分钟前
从饱和升温曲线到参数辨识:在LabVIEW中拟合y=a(1-e^(-bx))
算法·labview·labview知识·labview功能·labview程序
keyipatience37 分钟前
5种IO模型与阻塞IO,select,poll,epoll,LT和ET模式
linux·服务器·网络·数据结构·c++·算法
摆烂z1 小时前
定时任务同步数据--续跑&重试
java·mybatis·ai编程
Lost of 程序猿1 小时前
船岸数据同步链路实战:SendFlag、单调版本号与断网三天后的续传
后端·c#·asp.net
CAE虚拟与现实2 小时前
docker desktop中的build功能是要build什么
java·docker·容器