c#中的ref参数在栈上发生了什么
本文关键字:发生了 什么 中的 ref 参数 | 更新日期: 2023-09-27 18:09:51
我正在阅读一些关于WCF和IDispatchMessageInspector的c#文档,并且接口定义了一个通过引用传递的'Message'对象,以便可以操作。
当你通过ref传递一些东西而不是正常传递时,堆栈上实际发生了什么?
通过引用传递的不是对象,而是变量。
基本上,它将调用端用作参数的变量和您调用的方法中的参数别名:
public void Foo()
{
int x = 10;
Bar(ref x);
Console.WriteLine(x); // Prints 20
}
public void Bar(ref int y)
{
y = 20;
}
这里,x
和y
本质上是相同的变量——它们指向相同的存储位置。对x
所做的更改可以通过y
看到,反之亦然。(注意,在这种情况下,它是调用者的局部变量,但它不必是-如果你通过引用传递了实例变量,那么Bar
可能会调用另一个方法来改变相同的变量,然后y
将被视为"神奇地"改变…)
有关c#中参数传递的更多信息,请参阅我的文章
通过引用意味着您可以更改传递给项目的原始变量。它传递的是栈上变量的地址,而不是变量的值。
IL转储:
正如你实际上问的在堆栈上到底发生了什么这里有一个by ref和by value方法的IL转储:
.method private hidebysig instance void ByRef(string& s) cil managed
{
// Code size 9 (0x9)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldstr "New value"
IL_0007: stind.ref
IL_0008: ret
} // end of method Class1::ByRef
与
.method private hidebysig instance void ByValue(string s) cil managed
{
// Code size 9 (0x9)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "New value"
IL_0006: starg.s s
IL_0008: ret
} // end of method Class1::ByValue
正如您所看到的,主要的区别在于参数类型(string&
而不是string
),并且它执行了额外的步骤来间接加载和存储值。
简单的c#源代码如下所示供参考:
void ByRef(ref string s)
{
s = "New value";
}
void ByValue(string s)
{
s = "New value";
}
按值传递;在调用函数之前,生成值类型的副本并将其放在堆栈上。当你通过引用传递一些东西时,它的地址被压入堆栈,而不是创建一个副本,当你在函数中修改对象时,被修改的是原始对象,而不是副本。
它的工作方式是,当编译器看到参数通过ref传递时,将对变量的引用转换为间接内存访问。
例如,让我们假设在内存位置100你有一个整数123;现在,当你调用一个按值接受(默认值)的函数时,在函数调用之前,123的副本将被制作并压入堆栈,这意味着副本现在将位于(假设)160地址。然而,当你通过引用传递一些东西时,地址100将被压入堆栈,并将驻留在位置160上。
现在生成的指令将读取160来获取对象的位置,然后在100处修改数据。当您使用*操作符时,间接操作将发生与指针相同的情况。
by ref意味着你可以为传递的对象创建新的实例,按值不能这么做只能改变对象属性