一直以来对于在引用类型前加ref传递变量和不加ref传递引用类型变量的区别不是很清楚,最近看到网上一个朋友给的一道题,仔细思索和编写代码测试,终于明白其中区别了。现整理如下,以供自己和朋友们理解和记忆。
ref是使变量通过引用的方式传递,而引用类型本身也是通过引用方式传递变量,所以一直纠结在这两者的区别?经过思考、查证资料以及编写代码证实,两者的区别在于:
引用类型前不加ref时,只能修改所引用对象的值,而不能对所引用对象的引用进行更改。
而在引用类型前加ref时,不仅可以修改所引用对象的值,也可以改变对所引用对象的引用。
通过下面测试代码,能够很好的反映出来。代码中使用了非安全代码(指针),通过使用指针显示对象的内存地址,可以观察出对对象引用的变化。
不加ref传递:
1
class
Value
2 {
3 public int i = 15 ;
4 }
5 class Program
6 {
7 static unsafe void Main( string [] args)
8 {
9 Program t = new Program();
10 t.first();
11 }
12 public unsafe void first()
13 {
14 int i = 5 ;
15 Value v = new Value();
16 v.i = 25 ;
17
18 /// 查看V实例的地址
19 fixed ( int * pi = & (v.i))
20 {
21 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
22 }
23
24 second(v, i);
25
26 /// 执行完second函数后V实例的地址
27 fixed ( int * pi = & (v.i))
28 {
29 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
30 }
31
32 Console.WriteLine(v.i);
33 }
34 public unsafe void second(Value v, int i)
35 {
36 i = 0 ;
37 v.i = 20 ;
38
39 /// 重新指定前V实例的地址
40 fixed ( int * pi = & (v.i))
41 {
42 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
43 }
44
45 Value val = new Value();
46 v = val; /// 创建一个v的副本,指向val,因此v的值不会被val改变
47
48 /// val实例的地址
49 fixed ( int * pi = & (val.i))
50 {
51 Console.WriteLine( " val.i Address 0x{0:X} " , ( int )pi);
52 }
53
54 /// v的副本地址
55 fixed ( int * pi = & (v.i))
56 {
57 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
58 }
59 Console.WriteLine(v.i + " " + i + " " ); // 15,0 可以理解
60 }
61 }
2 {
3 public int i = 15 ;
4 }
5 class Program
6 {
7 static unsafe void Main( string [] args)
8 {
9 Program t = new Program();
10 t.first();
11 }
12 public unsafe void first()
13 {
14 int i = 5 ;
15 Value v = new Value();
16 v.i = 25 ;
17
18 /// 查看V实例的地址
19 fixed ( int * pi = & (v.i))
20 {
21 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
22 }
23
24 second(v, i);
25
26 /// 执行完second函数后V实例的地址
27 fixed ( int * pi = & (v.i))
28 {
29 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
30 }
31
32 Console.WriteLine(v.i);
33 }
34 public unsafe void second(Value v, int i)
35 {
36 i = 0 ;
37 v.i = 20 ;
38
39 /// 重新指定前V实例的地址
40 fixed ( int * pi = & (v.i))
41 {
42 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
43 }
44
45 Value val = new Value();
46 v = val; /// 创建一个v的副本,指向val,因此v的值不会被val改变
47
48 /// val实例的地址
49 fixed ( int * pi = & (val.i))
50 {
51 Console.WriteLine( " val.i Address 0x{0:X} " , ( int )pi);
52 }
53
54 /// v的副本地址
55 fixed ( int * pi = & (v.i))
56 {
57 Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
58 }
59 Console.WriteLine(v.i + " " + i + " " ); // 15,0 可以理解
60 }
61 }
运行结果如下:
在这里,由于不能改变对V引用对象的引用,因此在执行“V=Val”时,其实是创建了一个V对象的副本对象,然后将副本对象的引用指向Val对象。
加ref传递:
class
Value
{
public int i = 15 ;
}
class Program
{
static unsafe void Main( string [] args)
{
Program t = new Program();
t.first();
}
public unsafe void first()
{
int i = 5 ;
Value v = new Value();
v.i = 25 ;
/// 查看V实例的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
second( ref v, i);
/// 执行完second函数后V实例的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
Console.WriteLine(v.i);
}
public unsafe void second( ref Value v, int i)
{
i = 0 ;
v.i = 20 ;
/// 重新指定前V实例的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
Value val = new Value();
v = val; /// v直接指向val,v断开了与之前连接
/// val实例的地址
fixed ( int * pi = & (val.i))
{
Console.WriteLine( " val.i Address 0x{0:X} " , ( int )pi);
}
/// v的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
Console.WriteLine(v.i + " " + i + " " ); // 15,0 可以理解
}
}
{
public int i = 15 ;
}
class Program
{
static unsafe void Main( string [] args)
{
Program t = new Program();
t.first();
}
public unsafe void first()
{
int i = 5 ;
Value v = new Value();
v.i = 25 ;
/// 查看V实例的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
second( ref v, i);
/// 执行完second函数后V实例的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
Console.WriteLine(v.i);
}
public unsafe void second( ref Value v, int i)
{
i = 0 ;
v.i = 20 ;
/// 重新指定前V实例的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
Value val = new Value();
v = val; /// v直接指向val,v断开了与之前连接
/// val实例的地址
fixed ( int * pi = & (val.i))
{
Console.WriteLine( " val.i Address 0x{0:X} " , ( int )pi);
}
/// v的地址
fixed ( int * pi = & (v.i))
{
Console.WriteLine( " V.i Address 0x{0:X} " , ( int )pi);
}
Console.WriteLine(v.i + " " + i + " " ); // 15,0 可以理解
}
}
运行结果如下:
在这里的话,由于加了ref传递对象V,因此在执行“V=Val”时,V对象直接指向了Val对象,因此V对象的地址不再0x12D16A4,而是Val的地址0x12D36CC。