12

众所周知,C# 类对象被视为引用,那么当您将引用对象作为对方法的引用传递时会发生什么?假设我们有:

public class A { ... }

接着:

public void F(ref A a) { ... }

编译器是否发现它a已经是一个引用类型并保持这种状态,或者他创建了一个对该对象的新引用?

如果我们有这样的东西怎么办:

public void F(ref A a)
{
    F(ref a);
}

在这段代码中,除了显而易见StackOverflowException的,编译器是否创建了对引用的引用……来引用a哪个是引用对象?

4

6 回答 6

28

最好用一个例子来说明这一点:

public class C { public int P { get; set; } }
public class X
{
    static void M(C c1, C c2, ref C c3, ref C c4)
    {
      c1.P = 11;
      c2 = new C() { P = 12 };
      c3.P = 13;
      c4 = new C() { P = 14 };
    }
    static void Main()
    {
      C q1 = new C() { P = 1 };
      C q2 = new C() { P = 2 };
      C q3 = new C() { P = 3 };
      C q4 = new C() { P = 4 };
      M(q1, q2, ref q3, ref q4);
      Console.WriteLine(q1.P);
      Console.WriteLine(q2.P);
      Console.WriteLine(q3.P);
      Console.WriteLine(q4.P);
    }
}

怎么了?

q1 和 c1 指的是同一个对象,但是是不同的变量。变异 c1.P 变异 q1.P 因为两个变量都引用同一个对象,所以 q1 现在是 11。

q2 和 c2 指的是同一个对象,但是是不同的变量。变异 c2 不会变异 q2 因为 c2 和 q2 是不同的变量;改变一个不会改变另一个。q2 保持 2,新对象丢失。

q3 和 c3 是同一个变量的两个名称,因此指的是同一个对象。当您更改 c3.P 时,它会自动更改 q3.P,因为它们是同一事物的两个名称。

q4 和 c4 是同一个变量的两个名称,因此变异 q4 也会变异 c4。

那有意义吗?

不幸的是,“为此变量创建别名”的关键字是“ref”。如果它是“别名”,它会更清楚。

回答你的第二个问题:不,这不会产生引用链。让我们做一个更清楚的例子:

...
int c1 = 123;
M(ref c1);
...
void M1(ref int q1) { M2(ref q1); }
void M2(ref int q2) { M2(ref q2); }

这表示 c1 和 q1 是同一个变量的不同名称,而 q1 和 q2 是同一个变量的不同名称,因此 c1、q1 和 q2 都是彼此的别名。C# 中从来没有像 C++ 中那样的“对变量的引用”。

于 2013-03-10T22:23:07.793 回答
4

在像这样的电话中

F(ref a);   // ByRef parameter

该变量a由方法体“直接使用” F。只有一个存储位置。如果该方法F分配给它的参数,那么所有可以看到的人都可以a立即看到该分配。相反,如果有人(外部F)分配给awhile 方法F正在运行,那么参数 ofF将“突然”更改为新对象。

另一方面,在像

F(a);  // normal value parameter

首先将变量a复制到一个新变量中,然后在内部使用新变量F。现在,如果参数的类型F值类型(如 astructenum),则复制由 value完成。所以整个数据都被复制了。但是如果参数的类型是引用类型class(包括数组类型), interface, delegate),则拷贝只a涉及引用拷贝

要检查您对类型参数的值参数情况的理解,请class弄清楚这些方法的作用:

static void F1(List<int> list>)  // no ref modifier
{
    list.Clear();
}

static void F2(List<int> list>)  // no ref modifier
{
    list = new List<int>();
}

这是一些可能有趣的例子ref

static void G(ref string a, ref string b)
{
    if (string.Equals(a, b))
        b += "_unique";

    // Is it now safe to say that a and b are distinct?
    // No. Anyone could have changed either a or b by now.
    // For example, a and b could "alias" public fields visisble to other threads.
}

作为上述使用的示例G,考虑var x = "initial"; G(ref x, ref x);在这种情况下将与方法内部a一起更改的代码。bG

于 2013-03-10T22:43:13.333 回答
3

ref只是创建对原始值的引用。对于引用类型,“值”是变量内存的位置。当您使用ref该方法时,现在可以更改原始变量引用的内容。如果您随后对已经ref是第二种方法的参数执行相同操作,则只需与第一种方法具有相同的引用。

于 2013-03-10T22:26:05.887 回答
2

这两个概念并不相同。方法参数无论是值类型还是引用类型,都可以通过 ref 进行修改。

通过引用传递类型可以使被调用的方法修改参数所引用的对象或更改参数的存储位置。

static void Main()
{
    Foo item = new Foo("aaa");
    GetByReference(ref item);
    Console.WriteLine(item.Name)
}
static void ChangeByReference(ref Foo itemRef)
{
    itemRef = new Foo("bbb");
}

这实际上会打印“bbb”,因为在这种情况下,您没有更改对象值,而是更改了对象本身

于 2013-03-10T22:18:02.133 回答
2

当您将对象作为方法的参数传递时,您会传递一个引用原始对象的新指针。如果将对象作为 ref 参数传递,则传递使用调用者方法的相同指针。一个例子,

public void F(ref A a, A b){
    a = new A(1);
    b.Property = 12;
    b = new B(2);
}

public void G(){
    A a = new A(0);
    A b = new A(0);
    F(a,b);
    System.Console.WriteLine(a + " - " + b);
}

输出是 1 - 12 因为对象 b 的指针没有改变,但原始对象改变了。

于 2013-03-10T22:20:58.420 回答
1

简单地说,将变量作为 ref 参数传递就像为原始变量创建别名一样。

引用类型和引用参数是不同的实体。在 C# 中,变量总是按值传递。该值可能是对另一个对象或存储值的引用。

换句话说,引用类型是“通过引用传递”的,因为当您将对象实例传递给方法时,该方法会获得对该对象实例的引用。
在引用参数的情况下,引用是对变量的引用(因此认为这是一个别名是有意义的)。这是“通过引用传递”的另一种形式。

以你的例子为例:

public void F(ref A a)
{
    F(ref a);
}

这里就像我们有一个a被无限次引用的对象(原始参数)。(请注意,这不是实际发生的情况)。此图旨在提供处理参考参数时在幕后发生的事情的惯用表示。

在此处输入图像描述

有关详细信息,请参阅 4.0 C# 规范的第 1.6.6.1 节。

于 2013-03-10T22:10:02.193 回答