自从 C# 7.3 放开 ref 之后,这玩法就太花哨了,也让 C# 这门语言变得越来越多范式,越来越重,这篇我们就来聊聊 ref,本质上来说 ref 的放开就是把 C/C++ 指针的那一套又拿回来了,而且还封装成一套自己的玩法,下面一一解读下。
一:方法参数上的 ref
我想设计者的初心把 ref 的功能限制的死死的,可能也考虑到 C# 是一门面向业务开发的语言,讲究的是做项目快狠准,性能反而不是第一要素,这个时候的 ref 很简单,看一下代码:
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
拿的是栈地址,对栈地址上的值进行修改,自然就修改了指向这些地址上的变量,和引用类型原理一致,接下来我们从汇编角度去验证,在 Price 方法上下一个断点。
D:\net5\ConsoleApp4\ConsoleApp3\Program.cs @ 16:
026b048e 8d4dec lea 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
方法,后续的赋值将会影响这个栈位置
上的内容。
2. 方法返回值上的 ref
这就有意思了,进入的时候传地址,回来的时候也想传地址,很显然方法线程栈
上的 值类型
是传不出去的,毕竟方法返回后,esp,ebp 所控制的方法栈帧空间是要销毁的,所以只能是堆上对象才能实现。
为了方便理解,看如下代码:
class Program
{
static void Main(string[] args)
{
ref long price = ref 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 验证一下。
0:000> !dumpheap -type System.Int64[]
Address MT Size
027ca7b0 04c39d00 36
Statistics:
MT Count TotalSize Class Name
04c39d00 1 36 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++指针代码
。
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;
}
说实话,这代码看起来就清爽多了。
2. 对 ref 变量的 in 操作
这又是一套 C/C++ 的玩法,有时候不希望某一个方法对 ref 变量进行修改,注意:是不希望某一个方法进行修改,其他方法是可以的,那这个怎么实现呢?这就需要在入参上加 in
前缀,把代码修改一下。
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 即可。
void modifyprice(const long long* price) {
*price = 12;
printf("%d", *price);
}
总的来说,ref 这一套玩法太另类了
相关文章:
- [译]C# 7系列,Part 7: ref Returns ref返回结果 2021-06-20
- 聊聊web的底层-TCP 2022-01-02
- c# ref 2022-01-11
- C#系列之聊聊.Net Core的InMemoryCache 2018-08-22
- C# 语法糖 2021-11-21
- C# ref,out 2021-11-02
- C# ref与out 2021-09-17
- c# out ref 2021-07-12
- C# ref and out 2021-03-02
- C# ref out 2021-04-29
- C#语法糖系列 —— 第一篇:聊聊 params 参数底层玩法 2022-04-19
- c#参数修饰符-ref 2022-01-19
- out与ref修饰符 2021-11-11
- [译]C# 7系列,Part 9: ref structs ref结构 2021-06-20
- C#中参数修饰符ref,out ,params的区别 2021-10-20
- C# ref、out、params与值类型参数修饰符 2021-08-24
- C#的方法参数的无修饰符以及 in、out、ref(in/out) 修饰符的区别 2021-06-15
- C#学习系列-out与ref的区别 2021-11-24
- 聊聊web的底层 2021-09-05
- 参数修饰符 params、 out、ref 2022-01-25
- C#语法糖 2019-07-05
- Node.js 私聊系统算法 2018-07-18
- C# 类/方法修饰符 2012-06-24
- C# 列表理解 = 纯语法糖? 2010-10-31
- 带有 ref 修饰符的参数到底包含什么? 2017-11-11
- c# 语法糖重载 2011-09-01
- C/C++ 中的语法糖 2011-08-02
- 糖果聊天安装失败 2014-05-01
- Python 装饰器只是语法糖? [复制] 2012-08-31
- C# XNA 聊天框系统 2015-09-06
- C#语法糖类似于SQL的IN比较? 2012-07-06
- 如何在Web前端实现CAD图文字全文搜索功能之技术分享 2022-04-24
- Java 8的18个常用日期处理 2022-04-24
- TCP 连接的建立 & 断开 2022-04-24
- 如何向开源项目(Apache-InLong)提交代码 2022-04-24
- 开发、运维、业务都说好的全栈云原生长这样! 2022-04-24
- CRUSE: Convolutional Recurrent U-net for Speech Enhancement 2022-04-24
- 【原创】浅谈指针(十二)关于static(上) 2022-04-24
- SQL注入绕过总结 2022-04-24
- HCNP Routing&Switching之端口隔离 2022-04-24
- 【面试普通人VS高手系列】Redis和Mysql如何保证数据一致性 2022-04-24