c#中的ref参数在栈上发生了什么

本文关键字:发生了 什么 中的 ref 参数 | 更新日期: 2023-09-27 18:09:51

我正在阅读一些关于WCF和IDispatchMessageInspector的c#文档,并且接口定义了一个通过引用传递的'Message'对象,以便可以操作。

当你通过ref传递一些东西而不是正常传递时,堆栈上实际发生了什么?

c#中的ref参数在栈上发生了什么

通过引用传递的不是对象,而是变量

基本上,它将调用端用作参数的变量和您调用的方法中的参数别名:

public void Foo()
{
    int x = 10;
    Bar(ref x);
    Console.WriteLine(x); // Prints 20
}
public void Bar(ref int y)
{
    y = 20;
}

这里,xy本质上是相同的变量——它们指向相同的存储位置。对x所做的更改可以通过y看到,反之亦然。(注意,在这种情况下,它是调用者的局部变量,但它不必是-如果你通过引用传递了实例变量,那么Bar可能会调用另一个方法来改变相同的变量,然后y将被视为"神奇地"改变…)

有关c#中参数传递的更多信息,请参阅我的文章

通过引用意味着您可以更改传递给项目的原始变量。它传递的是栈上变量的地址,而不是变量的值。

IL转储:

正如你实际上问的在堆栈上到底发生了什么这里有一个by refby 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意味着你可以为传递的对象创建新的实例,按值不能这么做只能改变对象属性