关于C#:为什么在传递对象时使用”ref”关键字?

Why use the 'ref' keyword when passing an object?

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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",这意味着对象被作为引用传递。


如果要更改对象是什么,请传递一个ref

1
2
3
4
5
6
7
8
9
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而是指完全不同的对象。

如果要更改不可变对象的值,例如string,这也可能很有用。创建string后,不能更改它的值。但是,通过使用ref,可以创建一个函数来更改另一个具有不同值的字符串。

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

此外,当参数类型为对象时,对象变量始终充当对对象的引用。这意味着当使用ref关键字时,您得到了对引用的引用。这允许您按照上面给出的示例中的描述进行操作。但是,当参数类型为基元值(如int)时,如果该参数在方法内赋值,则在方法返回后,传入参数的值将发生变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

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

我写了一篇关于这个主题的相当长的文章,以避免每次出现在新闻组上时都要仔细地写:)


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

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

正如人们之前所说,任务是对引用的修改,因此会丢失:

1
2
3
4
5
6
7
public void Method1(object obj) {  
 obj = new Object();
}

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

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

稍微修改一下你的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 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; }
        }
    }


由于testref是一个类(它是引用对象),您可以在不将其作为引用传递的情况下更改t中的内容。但是,如果将t作为引用传递,则testref可以更改原始t引用的内容。也就是说,让它指向一个不同的对象。


ref你可以写:

1
2
3
4
static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

方法完成后,将更改t。


把参考类型(如List的变量(如foo看作是保存"object 24601"形式的对象标识符。假设声明foo = new List {1,5,7,9};导致foo持有"object 24601"(一个包含四个项目的列表)。然后打电话给foo.Length会询问对象24601的长度,它会回答4,所以foo.Length等于4。

如果在不使用ref的情况下将foo传递给某个方法,该方法可能会更改对象24601。由于这些变化,foo.Length可能不再等于4。但是,方法本身将无法更改foo,它将继续保留"object 24601"。

foo作为ref参数传递将允许被调用的方法不仅更改对象24601,还更改foo本身。该方法可以创建一个新的对象8675309,并将对该对象的引用存储在foo中。如果这样,foo将不再持有"object 24601",而是持有"object 8675309"。

实际上,引用类型变量不包含"object 8675309"形式的字符串;它们甚至不包含任何可以有意义地转换为数字的内容。尽管每个引用类型变量都将保存一些位模式,但是存储在这些变量中的位模式与它们标识的对象之间没有固定的关系。代码无法从一个对象或对它的引用中提取信息,随后确定另一个引用是否标识了同一个对象,除非代码持有或知道标识原始对象的引用。


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


ref模拟(或表现)为两个范围的全局区域:

  • 呼叫者
  • Callee。

通过对引用类型使用ref关键字,可以有效地传递对引用的引用。在许多方面,它与使用out关键字相同,但有一点小的区别,即无法保证该方法实际将任何内容分配给ref的ed参数。


ref表示函数是可以获取对象本身的指针,还是只能获取其值。

传递引用不绑定到语言;它是传递值、传递名称、传递需求等旁边的参数绑定策略。

旁注:在这个上下文中,类名TestRef是一个非常糟糕的选择;)。


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