我正在阅读一些关于WCF和IDispatchMessageInspector的C#文档,接口定义了一个'Message'对象,该对象通过引用传递,以便可以对其进行操作。
当您通过ref传递某些东西而不是正常传递时,堆栈上实际发生了什么?
答案 0 :(得分:5)
这不是通过引用传递的对象 - 它是变量。
基本上,它将来自主叫方的参数变量和您调用的方法中的参数别名:
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#中参数传递的更多信息,请参阅我的article on the subject。
答案 1 :(得分:4)
通过引用表示您可以更改传递给项目的原始变量。它基本上传递堆栈上变量的地址而不是变量值。
当你实际上要求堆栈上实际发生的事情时这里是一个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表示你可以为传递的对象创建新实例, 通过值不能这样做只是改变对象属性