为什么在传递对象时使用'ref'关键字?


290

如果将对象传递给方法,为什么要使用ref关键字?这不是默认行为吗?

例如:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

输出为“ Bar”,表示该对象已作为参考传递。

Answers:


298

ref如果要更改对象是什么,则传递a :

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

调用DoSomething之后,t不引用原始对象new TestRef,而是引用完全不同的对象。

如果您想更改不可变对象(例如)的值,这可能也很有用stringstring创建一次后,您将无法更改它的值。但是,通过使用ref,您可以创建一个函数,该函数将字符串更改为另一个具有不同值的字符串。

编辑:正如其他人所提到的。ref除非需要,否则使用它不是一个好主意。使用ref使方法可以自由更改其他参数,将需要对方法的调用者进行编码以确保他们能够处理这种情况。

同样,当参数类型是对象时,对象变量始终充当对对象的引用。这意味着ref使用关键字时,您将获得对引用的引用。这使您可以按照上面的示例进行操作。但是,当参数类型是原始值(例如int)时,如果在方法中将此参数分配给该参数,则在方法返回后,传入的参数值将更改:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

88

您需要区分“按值传递引用”和“按引用传递参数/参数”。

我已经写了一篇很长的文章,以避免每次在新闻组上出现问题时都必须认真撰写:)


1
好吧,我在将VB6升级到.Net C#代码时遇到了这个问题。有些函数/方法签名带有ref,out和plain参数。那么,如何更好地区分普通参数与参考参数之间的区别呢?
bonCodigo 2015年

2
@bonCodigo:不确定“更好的区分”是什么意思-它是签名的一部分,还必须ref在调用站点中指定...在其他地方还希望区分它?语义也相当清晰,但是需要仔细表达(而不是“对象通过引用传递”,这是普遍的过分简化)。
乔恩·斯基特

我不知道为什么Visual Studio仍然没有明确显示已通过的内容
MonsterMMORPG '04

3
@MonsterMMORPG:恐怕我不明白你的意思。
乔恩·斯基特

56

在.NET中,当您将任何参数传递给方法时,都会创建一个副本。在值类型中表示您对值所做的任何修改都在方法范围内,并且在退出方法时丢失。

传递引用类型时,也会创建一个副本,但它是引用的副本,即,现在您在内存中对同一对象有两个引用。因此,如果使用引用来修改对象,则该对象将被修改。但是,如果您修改引用本身-我们必须记住它是一个副本-那么退出该方法时所有更改也会丢失。

正如人们之前所说,分配是对引用的修改,因此丢失了:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

上面的方法不会修改原始对象。

对您的示例进行一些修改

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

6
我比接受的答案更喜欢这个答案。它更清楚地说明了在使用ref关键字传递引用类型变量时发生了什么。谢谢!
Stefan

17

由于TestRef是一个类(它们是引用对象),因此您可以更改t内部的内容而无需将其作为ref传递。但是,如果将t用作参考,TestRef可以更改原始t所指的内容。即使其指向另一个对象。



8

可以将foo引用类型(例如List<T>)的变量(例如)视为持有“ Object#24601”形式的对象标识符。假设该语句foo = new List<int> {1,5,7,9};导致foo保存“ Object#24601”(包含四个项目的列表)。然后调用foo.Length将询问对象#24601的长度,并将响应4,因此foo.Length等于4。

如果foo不使用传递给方法ref,则该方法可能会更改对象#24601。此类更改的结果foo.Length可能不再等于4。但是,方法本身将无法更改foo,而该方法将继续保留“ Object#24601”。

foo作为ref参数传递将允许被调用的方法不仅可以更改对象#24601,还可以更改foo自身。该方法可能会创建一个新的Object#8675309并将对其的引用存储在中foo。如果这样做,foo将不再保存“ Object#24601”,而是保存“ Object#8675309”。

实际上,引用类型的变量不包含“ Object#8675309”形式的字符串;他们甚至没有任何可以有意义地转换为数字的东西。即使每个引用类型的变量都将保留某些位模式,但存储在此类变量中的位模式与它们标识的对象之间没有固定的关系。代码无法从对象或对其的引用中提取信息,然后再确定另一个引用是否标识了相同的对象,除非代码持有或知道标识了原始对象的引用。


5

这就像将指针传递给C中的指针一样。在.NET中,这将允许您更改原始T所指的内容,尽管我个人认为,如果在.NET中这样做,可能会遇到设计问题!


3

通过将ref关键字与引用类型一起使用,可以有效地将引用传递给引用。在很多方面,它与使用out关键字相同,但有一点细微的差别,即不能保证该方法实际上会将任何内容分配给ref'ed参数。


3

ref 仅针对两个范围将(或作为)模拟为全局区域:

  • 呼叫者
  • 被告

1

但是,如果传递值,则情况会有所不同。您可以强制值通过引用传递。例如,这允许您将整数传递给方法,并让该方法代表您修改整数。


4
无论您要传递引用还是值类型值,默认行为都是按值传递。您只需要了解引用类型,您传递的值就是一个引用。这与通过引用传递不同。
乔恩·斯基特

1

Ref表示该函数是可以使用对象本身还是仅使用其值。

通过引用传递不限于一种语言;这是在值传递,名称传递,需求传递等旁边的参数绑定策略。

旁注:TestRef在这种情况下,类名是一个糟糕的选择;)。

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.