我理解如果我将值类型(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";
}
答案 0 :(得分:144)
您可以更改foo
指向使用y
的内容:
Foo foo = new Foo("1");
void Bar(ref Foo y)
{
y = new Foo("2");
}
Bar(ref foo);
// foo.Name == "2"
答案 1 :(得分:29)
在某些情况下,您希望修改实际的引用,而不是指向的对象:
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]);
答案 2 :(得分:17)
Jon Skeet写了关于C#参数传递的a great article。它详细说明了按值,引用(ref
)和输出(out
)传递参数的确切行为和用法。
以下是该页面中与ref
参数相关的重要引用:
参考参数不通过 中使用的变量的值 函数成员调用 - 他们使用 变量本身。而不是 为...创建新的存储位置 函数成员中的变量 声明,相同的存储位置 使用,所以变量的值 在函数成员和值中 参考参数将始终 是相同的。参考参数需要 ref修饰符作为两者的一部分 声明和调用 - 那 意味着你什么时候总是很清楚 通过引用传递内容。
答案 3 :(得分:15)
这里非常好解释: http://msdn.microsoft.com/en-us/library/s6938f28.aspx
文章摘要:
引用类型的变量不直接包含其数据;它 包含对其数据的引用。传递引用类型时 参数值,可以更改指向的数据 引用,例如类成员的值。但是,你 不能改变引用本身的值;也就是说,你做不到 使用相同的引用为新类分配内存并拥有它 在街区外坚持。为此,请使用以下命令传递参数 引用或退出关键字。
答案 4 :(得分:9)
使用ref关键字传递引用类型时,通过引用传递引用,并且调用的方法可以为参数指定新值。该更改将传播到调用范围。如果没有引用,引用将按值传递,这不会发生。
C#也有'out'关键字,它与ref非常相似,除了使用'ref'之外,必须在调用方法之前初始化参数,并且使用'out'必须在接收方法中指定一个值。 / p>
答案 5 :(得分: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();
}
答案 6 :(得分: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;
}
}
答案 7 :(得分:3)
除现有答案外:
当您询问两种方法的区别时:使用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
}
时没有co(ntra)方差:
<?php
$string = "0 3 28 36 42 66 73 80 103 125 997 1003 1027 2006 3001";
$data= explode( ' ', $string );
$multiple = 1;
foreach( $data as $value )
{
if( (int)$value > ( 1000 * $multiple ) )
{
// Passed a 1000 marker
echo $value."\n";
$multiple++;
}
}
答案 8 :(得分:1)
方法中的参数似乎总是传递副本,问题是什么的副本。副本由对象的复制构造函数完成,因为所有变量都是C#中的Object,我相信所有这些都是这种情况。变量(对象)就像生活在某些地址的人一样。我们要么改变居住在这些地址的人们,要么我们可以为电话簿中那些地址的人们创建更多的参考资料(制作浅层副本)。因此,多个标识符可以引用相同的地址。引用类型需要更多空间,因此与通过箭头直接连接到堆栈中的标识符的值类型不同,它们具有堆中另一个地址的值(更大的空间)。这个空间需要从堆中获取。
值类型: 标识符(包含值=堆栈值的地址)----&gt;值类型的值
参考类型: 标识符(包含值=堆栈值的地址)----&gt;(包含值=堆值的地址)----&gt;堆值(通常包含其他值的地址),想象更多箭头粘贴在不同的方向到Array [0],Array [1],array [2]
更改值的唯一方法是按箭头操作。如果一个箭头丢失/更改的方式无法访问该值。
答案 9 :(得分:-1)
引用变量将地址从一个地方传送到另一个地方,因此在任何地方对其进行的任何更新都会在所有地方反映出来,那么REF的用途是什么。 直到没有新的内存分配给在方法中传递的参考变量之前,参考变量(405)才是好的。
一旦新存储器分配(410),则该对象(408)上的值改变将不会在所有地方反映。 对于这个裁判来了。 Ref是引用的引用,因此每当新内存分配时,它就会知道,因为它指向该位置,因此该值可以由EveryOne共享。您可以看到图像以更加清晰。