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

如果我将一个对象传递给一个方法,为什么我应该使用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”,这意味着对象作为引用传递。

183082 次浏览

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

使用ref你可以写:

static public void DoSomething(ref TestRef t)
{
t = new TestRef();
}

t将在方法完成后更改。

如果你想改变对象的内容,则传递ref:

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)时,如果该形参在方法中赋值,则传入的实参值将在方法返回后更改:

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;
}

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

通过对引用类型使用ref关键字,可以有效地将引用传递给引用。在许多方面,这与使用out关键字是相同的,但有一个微小的区别,即不能保证该方法实际会将任何东西赋值给ref'ed形参。

这就像在c中传递一个指针给另一个指针一样,在。net中,这将允许你改变原始T所指向的就我个人而言,尽管我认为如果你在。net中这样做,你可能有一个设计问题!

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

我已经写了一个这是一篇相当长的文章,以避免每次在新闻组上出现时都必须仔细地写

在. 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; }
}
}

ref模拟(或行为)为两个作用域的全局区域:

  • 调用者
  • 调用。

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

如果foo被传递给一个没有使用ref的方法,该方法可能会对Object #24601进行更改。作为这些变化的结果,foo.Length可能不再等于4。然而,方法本身将无法更改foo,它将继续保存“Object #24601”。

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

在实践中,引用类型变量不会保存“Object #8675309”;;它们甚至没有任何可以有意义地转换成数字的东西。尽管每个引用类型变量都会保存一些位模式,但存储在这些变量中的位模式与其标识的对象之间并没有固定的关系。代码不可能从对象或对其的引用中提取信息,然后确定另一个引用是否标识了相同的对象,除非代码持有或知道标识原始对象的引用。