使用c#中的ref参数在堆栈上发生了什么?

时间:2011-09-28 10:52:37

标签: c# stack ref-parameters

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

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

4 个答案:

答案 0 :(得分:5)

这不是通过引用传递的对象 - 它是变量

基本上,它将来自主叫方的参数变量和您调用的方法中的参数别名:

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#中参数传递的更多信息,请参阅我的article on the subject

答案 1 :(得分:4)

通过引用表示您可以更改传递给项目的原始变量。它基本上传递堆栈上变量的地址而不是变量值。

IL Dump:

当你实际上要求堆栈上实际发生的事情时这里是一个IL转储 by ref by value 方法:

.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

VS

.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";
}

答案 2 :(得分:0)

当你按价值传递东西时;在调用函数之前,将创建值类型的副本并将其置于堆栈中。当你通过引用传递一些东西时,它的地址被推送到堆栈而不是创建一个副本,当你在函数中修改对象时,原始对象被修改而不是副本。

它的工作方式是编译器在看到参数由ref传递时,将对变量的引用转换为间接内存访问。

例如,让我们假设在内存位置100你有一个整数123;现在,当你调用一个按值接受的函数(这是默认值)时,将调用123的副本并在调用函数之前将其推送到堆栈,这意味着副本现在将处于(假设)160地址。但是,当您通过引用传递某些内容时,地址100将被推送到堆栈并将驻留在位置160上。

现在生成的指令将读取160以获取对象的位置,然后在100处修改数据。间接将与使用*运算符时对指针的操作相同。

答案 3 :(得分:-1)

通过ref表示你可以为传递的对象创建新实例, 通过值不能这样做只是改变对象属性