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


176

我了解,如果我将值类型(intstruct等)作为参数(没有ref关键字)传递,则该变量的副本将传递给方法,但是如果我使用ref关键字,则会传递对该变量的引用,不是新的。

但是对于引用类型(如类),即使没有ref关键字,引用也会传递给方法,而不是副本。那么ref关键字与引用类型的用法是什么?


举个例子:

var x = new Foo();

以下内容有什么区别?

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

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

Answers:


154

您可以更改foo使用的要点y

Foo foo = new Foo("1");

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

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

17
因此,您基本上可以得到原始参考的参考
lhahne

2
您可以更改原始引用“所指”的内容,所以可以。
user7116

1
克里斯,你的解释很棒。感谢您帮助我理解这个概念。
安德烈亚斯·格雷希

4
因此,在对象上使用'ref'就像在C ++中使用双指针一样?
汤姆·黑兹

1
@TomHazel:-ish,前提是您在C ++中使用“双”指针来更改指针指向的内容。
user7116 2012年


21

乔恩·斯凯特(Jon Skeet)写了一篇很棒的文章,介绍C#中的参数传递。它清楚地详细说明了按值,按引用(ref)和按输出(out)。

这是该页面中与ref参数有关的重要报价:

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


11
我喜欢将狗皮带牵引给朋友以传递参考值的类比...虽然它很快就分解了,因为我认为您可能会注意到,如果您的朋友在将您的狗狗换成杜宾犬之前将其交换给了杜宾犬,皮带;-)
corlettk 2009年

16

很好的解释在这里:http : //msdn.microsoft.com/en-us/library/s6938f28.aspx

文章摘要:

引用类型的变量不直接包含其数据。它包含对其数据的引用。当按值传递引用类型参数时,可以更改引用所指向的数据,例如类成员的值。但是,您不能更改引用本身的值。也就是说,您不能使用相同的引用来为新类分配内存并使它持久存在于块外。为此,请使用ref或out关键字传递参数。


4
的解释确实是非常好的。但是,在SO上不鼓励仅链接的答案。我在文章中添加了摘要,以方便读者阅读。
Marcel

10

当使用ref关键字传递引用类型时,您将按引用传递引用,并且调用的方法可以为该参数分配新值。该更改将传播到调用范围。如果没有ref,则引用将按值传递,并且不会发生。

C#还具有与ref非常相似的'out'关键字,除了使用'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();
}

如果您不关心传入的引用,也可以用完

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

除了现有的答案:

正如您要求的两种方法的区别一样:使用ref或时,没有co(ntra)方差out

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#中的Object,因此我相信所有情况都是如此。变量(对象)就像居住在某些地址的人。我们可以更改居住在这些地址的人员,也可以在电话簿中创建对居住在这些地址的人员的更多引用(抄录较浅)。因此,一个以上的标识符可以引用相同的地址。引用类型需要更多的空间,因此与通过箭头直接将其连接到堆栈中的标识符的值类型不同,它们对堆中的另一个地址(有更大的驻留空间)具有值。该空间需要从堆中获取。

值类型:标识符(包含值=堆栈值的地址)---->值类型的值

参考类型:标识符(包含值=堆栈值的地址)---->(包含值=堆值的地址)---->堆值(最常包含其他值的地址),想象更多的箭头贴在不同的位置指向Array [0],Array [1],Array [2]的方向

更改值的唯一方法是遵循箭头。如果一个箭头丢失/更改的方式,则该值不可访问。


-1

参考变量将地址从一个地方传送到另一个地方,因此在任何地方对其进行的任何更新都会在所有地方反映出来,那么REF的用途是什么。直到没有新的内存分配给该方法中传递的参考变量之前,参考变量(405)才是好的。

一旦分配了新的存储器(410),则该对象上的值改变(408)将不会在所有地方反映出来。对于这个裁判来了。Ref是引用的引用,因此每当分配新内存时,它就会知道,因为它指向该位置,因此该值可以由EveryOne共享。您可以查看图像以更加清晰。

参考变量中的参考

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.