C#中引用类型变量的“ref”有什么用?

210

我知道如果作为参数传递值类型 (int, struct, 等等),如果没有使用 ref 关键字,那么会传递该变量的副本到方法中;但是如果使用了 ref 关键字,则传递的是该变量的引用,而不是新的变量。

但对于引用类型,比如类,即使没有使用 ref 关键字,也会传递一个引用到方法中,而不是一个副本。那么,对于引用类型,使用 ref 关键字有什么用处呢?


以以下代码为例:

var x = new Foo();

以下两者有何区别?

void Bar(Foo y) {
    y.Name = "2";
}

void Bar(ref Foo y) {
    y.Name = "2";
}
10个回答

191
你可以使用 y 来改变 foo 指向的内容:
Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"

22
所以您基本上会得到对原始引用的引用。 - lhahne
2
你可以更改原始引用所“引用”的内容,所以是的。 - user7116
1
Chris,你的解释很好;感谢你帮助我理解这个概念。 - Andreas Grech
8
在对象上使用'ref'就像在C ++中使用双指针一样? - Tom Hazel
4
如果你在C++中使用“double”指针来改变指针所指向的内容,那么“-ish”的语法就可以派上用场。 - user7116
显示剩余2条评论

37

有时候你想要修改实际的引用而不是指向的对象:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);

1
这个答案提供了一个情况,说明为什么我需要更改原始引用而不是设置先前实例的属性值。 - Chuck Lu

23

Jon Skeet写了一篇关于C#参数传递的精彩文章。它清楚地详细介绍了按值、按引用(ref)和按输出(out)传递参数的确切行为和用法。

以下是该页面中与ref参数相关的重要引用:

引用参数不传递函数成员调用中使用的变量的值 - 它们使用变量本身。它们不会为函数成员声明中的变量创建新的存储位置,而是使用相同的存储位置,因此函数成员中的变量值和引用参数的值将始终相同。引用参数需要在声明和调用中都使用ref修饰符 - 这意味着当您通过引用传递某些内容时,这总是很清楚明白的。


11
我喜欢将把你的狗链传递给朋友的比喻用于解释按值传递引用...不过这个比喻很快就会失效,因为如果你的朋友在将狗链还给你之前把你的杂种狗换成了德国牧羊犬,我认为你可能会注意到;-) - corlettk

22
非常清晰地解释了这里: http://msdn.microsoft.com/en-us/library/s6938f28.aspx 文章摘要:
引用类型的变量不直接包含其数据; 它包含对其数据的引用。当您通过值传递引用类型参数时,可以更改由引用指向的数据,例如类成员的值。但是,您无法更改引用本身的值; 也就是说,您不能使用相同的引用为新类分配内存并使其在块之外持续存在。要做到这一点,请使用 ref 或 out 关键字传递参数。

5
解释的确很好。然而,在SO上,仅提供链接的答案是不受欢迎的。我添加了一份摘要来方便读者。 - Marcel

10

使用ref关键字传递引用类型时,您通过引用传递引用,所调用的方法可以将新值分配给参数。这种更改将向调用范围传播。没有ref,引用按值传递,这不会发生。

C#还有“out”关键字,它很像ref,只是使用'ref'时,在调用方法之前必须初始化参数,在使用'out'时,必须在接收方法中分配一个值。


5

它允许您修改传递的引用。例如:

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

如果您不关心传递的引用,也可以使用out

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}

4

另一堆代码

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}

3
除了现有的答案之外: 你问到两种方法的区别:使用 refout 时没有协变性:
class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}

1
一个方法中的参数似乎总是传递一个副本,问题是副本是什么。对象的复制由复制构造函数完成,由于C#中所有变量都是对象,我认为对于它们来说情况也是如此。变量(对象)就像住在某些地址的人。我们可以更改住在那些地址的人,或者我们可以在电话簿中创建更多对住在那些地址的人的引用(进行浅复制)。因此,超过一个标识符可以指向同一地址。引用类型需要更多的空间,因此与值类型不同,它们不会直接通过箭头连接到堆栈中的标识符,而是具有另一个地址的值(一个更大的空间)。这个空间需要从堆中获取。
值类型: 标识符(包含值=堆栈值的地址)---->值类型的值
引用类型: 标识符(包含值=堆栈值的地址)---->(包含值=堆值的地址)---->堆值(最常包含指向其他值的地址),想象一下更多的箭头朝着Array[0]、Array[1]、Array[2]等不同方向延伸。
改变值的唯一方法是跟随箭头。如果在路上有一个箭头丢失/改变,那么该值就无法访问。

-1

引用变量携带地址从一个地方到另一个地方,因此在任何地方对它们进行的任何更新都将反映在所有地方,那么 REF 的用途是什么? 引用变量(405)在未为传递给方法的引用变量分配新内存之前很好。

一旦分配了新内存(410),则对该对象(408)的值更改将不会在任何地方都反映出来。 为此,使用 ref。Ref 是引用的引用,因此每当分配新内存时,它就会知道,因为它指向该位置,因此该值可以被每个人共享。您可以查看图像以获得更清晰的说明。

Ref in Reference Variable


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接