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

相关推荐
芒果披萨几秒前
El表达式和JSTL
java·el
白榆maple9 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少13 分钟前
数据结构——线性表与链表
数据结构·c++·算法
duration~41 分钟前
Maven随笔
java·maven
zmgst44 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射