众所周知,C#classes对象被视为引用,那么当您将引用对象作为对方法的引用传递时会发生什么?说我们有:
public class A { ... }
然后:
public void F(ref A a) { ... }
编译器是否发现a
已经是引用类型并保持这种方式,或者他创建了对该对象的新引用?
如果我们有这样的事情怎么办?
public void F(ref A a)
{
F(ref a);
}
在此代码中,除了显而易见的StackOverflowException
之外,编译器是否创建对引用的引用...以引用作为引用对象的a
?
答案 0 :(得分:27)
最好用一个例子来说明:
public class C { public int P { get; set; } }
public class X
{
static void M(C c1, C c2, ref C c3, ref C c4)
{
c1.P = 11;
c2 = new C() { P = 12 };
c3.P = 13;
c4 = new C() { P = 14 };
}
static void Main()
{
C q1 = new C() { P = 1 };
C q2 = new C() { P = 2 };
C q3 = new C() { P = 3 };
C q4 = new C() { P = 4 };
M(q1, q2, ref q3, ref q4);
Console.WriteLine(q1.P);
Console.WriteLine(q2.P);
Console.WriteLine(q3.P);
Console.WriteLine(q4.P);
}
}
会发生什么?
q1和c1指的是同一个对象,但它们是不同的变量。变异c1.P会改变q1.P,因为两个变量都引用同一个对象,所以q1现在是11。
q2和c2指的是同一个对象,但它们是不同的变量。突变c2不会突变q2,因为c2和q2是不同的变量;换一个不会改变另一个。 q2保持2,新对象丢失。
q3和c3是同一个变量的两个名称,因此引用同一个对象。当你改变c3.P时会自动改变q3.P,因为它们是同一个东西的两个名字。
q4和c4是同一个变量的两个名称,因此变异q4也会使c4变异。
这有意义吗?
不幸的是,“为这个变量做一个别名”的关键字是“ref”。如果它是“别名”,那就更清楚了。
回答你的第二个问题:不,这不是一个引用链。让我们做一个更明确的例子:
...
int c1 = 123;
M(ref c1);
...
void M1(ref int q1) { M2(ref q1); }
void M2(ref int q2) { M2(ref q2); }
这表示c1和q1是同一变量的不同名称,q1和q2是同一变量的不同名称,因此c1,q1和q2都是彼此的别名。在C#中,C#中没有“引用变量”的引用。
答案 1 :(得分:4)
在像
这样的电话中F(ref a); // ByRef parameter
变量a
由方法F
的主体“直接使用”。只有一个存储位置。如果方法F
分配给其参数,那么立即可以看到a
的所有人都可以看到该分配。相反,如果某人(F
之外)在方法a
正在运行时分配给F
,则F
的参数将突然变为新对象”
另一方面,在像
这样的电话中F(a); // normal value parameter
首先将变量a
复制到新变量,然后在F
内使用新变量。现在,如果F
参数的类型是值类型(如struct
或enum
),则 副本 按值完成。所以整个数据都被复制了。但是,如果参数的类型是引用类型(class
(包括数组类型),interface
,delegate
),则{{1}的副本}仅涉及参考的 副本 。
要使用a
类型参数检查您对值参数大小写的理解,请确定这些方法的作用:
class
这是static void F1(List<int> list>) // no ref modifier
{
list.Clear();
}
static void F2(List<int> list>) // no ref modifier
{
list = new List<int>();
}
的一些可能有趣的例子:
ref
作为上述static void G(ref string a, ref string b)
{
if (string.Equals(a, b))
b += "_unique";
// Is it now safe to say that a and b are distinct?
// No. Anyone could have changed either a or b by now.
// For example, a and b could "alias" public fields visisble to other threads.
}
使用的示例,请考虑代码G
,在这种情况下,var x = "initial"; G(ref x, ref x);
将与方法a
中的b
一起更改
答案 2 :(得分:3)
ref
只是创建对原始值的引用。使用引用类型,“value”是变量内存的位置。当您使用ref
时,该方法现在可以更改原始变量引用。如果您对已经ref
的参数执行相同的操作,则第二个方法只具有与第一个方法相同的引用。
答案 3 :(得分:2)
这两个概念并不相同。无论是值类型还是引用类型,都可以通过ref修改方法参数。
通过引用传递类型使被调用方法能够修改参数引用的对象或更改参数的存储位置。
static void Main()
{
Foo item = new Foo("aaa");
GetByReference(ref item);
Console.WriteLine(item.Name)
}
static void ChangeByReference(ref Foo itemRef)
{
itemRef = new Foo("bbb");
}
这实际上会打印“bbb”,因为在这种情况下你没有改变对象值,但你改变了对象本身
答案 4 :(得分:2)
将对象作为方法的参数传递时,将传递一个引用原始对象的新指针。如果将对象作为ref参数传递,则传递使用调用方法的相同指针。一个例子,
public void F(ref A a, A b){
a = new A(1);
b.Property = 12;
b = new B(2);
}
public void G(){
A a = new A(0);
A b = new A(0);
F(a,b);
System.Console.WriteLine(a + " - " + b);
}
输出为1 - 12,因为对象b的指针不会改变,但原始对象会改变。
答案 5 :(得分:1)
简单地说,将变量作为ref参数传递就像为原始变量创建别名一样。
引用类型和引用参数是不同的实体。在C#中,变量总是按值传递。该值可以是对另一个对象或存储值的引用。
换句话说,引用类型是“通过引用传递”,因为当您将对象实例传递给方法时,该方法将获得对对象实例的引用。
在参考参数的情况下,引用是变量(因此,为什么认为这是一个别名是有意义的)。这是“通过引用传递”的另一种形式。
按照你的例子:
public void F(ref A a)
{
F(ref a);
}
这就像我们有一个无限次引用的单个对象(原始参数a
)。 (请注意,这不是实际发生的事情)。该图旨在提供在处理参考参数时在幕后发生的事情的惯用表示。
有关详细信息,请参阅4.0 C#规范的1.6.6.1节。