我正在阅读一些关于 WCF 和 IDispatchMessageInspector 的 C# 文档,并且该接口定义了一个通过引用传递的“消息”对象,以便可以对其进行操作。
当您通过 ref 传递某些东西而不是正常传递时,堆栈上实际发生了什么?
我正在阅读一些关于 WCF 和 IDispatchMessageInspector 的 C# 文档,并且该接口定义了一个通过引用传递的“消息”对象,以便可以对其进行操作。
当您通过 ref 传递某些东西而不是正常传递时,堆栈上实际发生了什么?
通过引用方式,您可以更改传递给项目的原始变量。它基本上传递堆栈上变量的地址而不是变量值。
正如您实际上问的那样,堆栈上实际发生的事情是按引用和按值方法的 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";
}
这不是通过引用传递的对象- 它是变量。
基本上,它为调用方用作参数的变量以及您调用的方法中的参数起别名:
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# 中参数传递的更多信息,请参阅我关于该主题的文章。
当您按值传递某些东西时;在调用函数之前制作值类型的副本并将其放入堆栈。当您通过引用传递某些内容时,该地址被推入堆栈而不是创建副本,并且当您修改函数中的对象时,原始对象将被修改而不是副本。
它的工作方式是当编译器看到参数由 ref 传递时,编译器会将您对变量的引用转换为间接内存访问。
例如让我们假设在内存位置 100 你有一个整数 123;现在,当您调用一个按值接受的函数(这是默认值)时,将在调用该函数之前制作 123 的副本并将其推送到堆栈上,这意味着该副本现在将位于(比如说)160 地址。但是,当您通过引用传递某些内容时,地址 100 将被压入堆栈并驻留在位置 160。
现在生成的指令将读取 160 以获取对象的位置,然后在 100 处修改数据。间接将发生与使用 * 运算符时对指针所做的相同。
通过 ref 意味着您可以为传递的对象创建新实例,按值不能只更改对象属性