不,这个不正确。
ref 带来的是引用语义。
当您调用函数 foo(T arg) - 无论 T 是值类型还是引用类型(我怎么强调都不为过) - foo 会得到 T 的副本。
值类型和引用类型的区别在于,值类型的names指的是有趣的东西,而引用类型的names指的是引用(或指针)有趣的东西。因此,当你复制一个值类型的对象时,你会得到一个有趣的东西的副本,当你复制对有趣的东西的引用时。
我在 Google 上找到的一些图片,而不是我自己画的:
图像中的副本基本上是您调用foo(T arg时发生的情况@)正在谈论。在这两种情况下,我们arg 都包含传递给foo 的内容的副本。如果T 是int,这就是您感兴趣的数字。如果T 是Circle,那么arg 是参数传递给foo 的“地址”1包含。我们可以谈论箭头,而不是谈论指针或引用。引用类型的变量包含指向有趣内容的箭头,而值类型的变量包含内容本身。
以下代码证明了这一点:
using System;
class MainClass {
public static void Main (string[] args) {
Foo foo1 = new Foo();
foo1.i = 1;
Console.WriteLine("Main(): foo1.i: {0}", foo1.i);
doStuffToFoo(foo1, 5);
Console.WriteLine("Main(): foo1.i: {0}", foo1.i);
Console.WriteLine();
Bar bar1;
bar1.j = 1;
Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
doStuffToBar(bar1, 5);
Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
}
static void doStuffToFoo(Foo aFoo, int aInt) {
aFoo.i = aInt;
aFoo = new Foo();
aFoo.i = 123;
Console.WriteLine("doStuffToFoo: aFoo.i: {0}", aFoo.i);
}
static void doStuffToBar(Bar aBar, int aInt) {
aBar.j = aInt;
aBar = new Bar();
aBar.j = 123;
Console.WriteLine("doStuffToBar: aBar.j: {0}", aBar.j);
}
}
class Foo {
public int i;
}
struct Bar {
public int j;
}
在尝试或阅读之前,看看您是否理解它打印的内容。
doStuffToFoo 中的变量aFoo 是foo1 的副本。干净利落。 aFoo 和 foo1 是不同的变量(存储在堆栈上的不同位置 - Main 的堆栈帧中的 foo1 和 doStuffToFoo 的堆栈帧中的 aFoo - 假设有一个堆栈,因为the stackis an implementationdetail),它们都包含指向同一个 Foo 对象的箭头。
当您引用这两个变量的字段时,它们通过各自的箭头访问相同的Foo 对象。但是当您将new Foo() 分配给aFoo 时,它只会将aFoo 中的箭头更改为指向新的Foo 对象。它对指向第一个Foo 对象的另一个箭头没有任何作用,它安全地存储在foo1 变量中,并且无法访问doStuffToFoo 中的代码。这就是为什么您在 Main 中看不到“123”的原因。
所有这些都将我们带到ref。 ref T v 表示 v 不是类型 T 的新变量(如果 T 是值类型,则包含一些有趣的东西;如果 T 是引用类型,则指向一些有趣的东西的箭头)。而v 是作为参数传递的任何内容的别名。它是其他变量的第二个名称。
如果您将以下代码添加到上面的示例中,您会看到它生效。
/***** inside Main: *****/
Foo foo2 = new Foo();
Foo foo3 = foo2;
foo2.i = 1;
Console.WriteLine("Main(): foo2.i: {0}", foo2.i);
Console.WriteLine("Main(): foo3.i: {0}", foo3.i);
doStuffToFooRef(ref foo2, 5);
Console.WriteLine("Main(): foo2.i: {0}", foo2.i);
Console.WriteLine("Main(): foo3.i: {0}", foo3.i);
/***** until here *****/
static void doStuffToFooRef(ref Foo aFoo, int aInt) {
aFoo.i = aInt;
aFoo = new Foo();
aFoo.i = 123;
Console.WriteLine("doStuffToFooRef: aFoo.i: {0}", aFoo.i);
}
现在aFoo 是foo2 的另一个名称。当您分配给“aFoo”时,您分配给“foo2”,因为这两个名称都引用同一个变量(存储位置)。这就是在doStuffToFooRef 分配期间发生的事情:
(i 仍然为零,因为它在我们放在那里之前是 123。)
在这里试试:https://repl.it/E80a
C# ref 在这方面就像 C++ 引用,正如 Александр Лысенко 在他的回答中所写的那样。虽然从技术上讲,ref SomeReferenceType x 可能是“指向指针的指针”,正如某些人所说,但这并没有抓住ref 的真正含义,而是详述了不需要的实现细节总是正确的(它可能会被优化)。这意味着添加ref 会添加一个指针。那么引用类型的 ref-of-a-ref 是指向指针的指针吗?每个额外的ref 是否都会增加一个间接级别?
1 就本次讨论而言,我们并不关心它是否真的是一个地址或其他东西。由于对象可能在内存中移动,我们理解它可能不是主机/操作系统的虚拟内存地址,而是广义上的地址。
有些人区分引用和指针,后者是数字内存地址,但其他人也不害怕将这些东西称为指针,因为它们指向对象。例如,Java 语言规范 (§4.3.1) 明确表示:
引用值(通常只是references)是指向这些对象的指针,以及一个特殊的空引用,它不引用任何对象。
他们强调“引用”——他们引入的新术语——但在他们看来,指针对读者来说是显而易见的,而且对象也不是不可移动的 AFAIK。