//在栈中增加了一个指针,指向新建的 object 对象
把 ref 和 out 关键字说透 (二)
}
//在栈中增加了一个指针,指向新建的 object 对象
}
传入引用类型的目的是把一个已经存在的对象的地址传过去,而如果你只是进行了 object a 声明,并没做复制,这行代码跟没做任何事!
所以,除非你使用了 out 关键字,在不用关键字和用 ref 关键字的情况下,你都必须事先复制。 out 只是一种特殊的 return
现在你是否明白,当变量什么情况下该用什么关键字了吗?其实有时候 ref 和 out 都可以达到目的,你需要根据你的初衷,和它们的特点,来衡量一下到底使用哪个了!
另外,我们来看看两个同样的函数,用 out 和 ref 时候的 IL 代码
原函数:
|
1
2
3
4
5
6
7
8
|
private
static
void
Test1(
out
int
a)
{
a = 1;
}
private
static
void
Test2(
ref
int
a)
{
a = 1;
}
|
IL代码:
|
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
|
.method
private
hidebysig
static
void
Test1 (
[
out
] int32& a
) cil managed
{
// Method begins at RVA 0x2053
// Code size 5 (0x5)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.1
IL_0003: stind.i4
IL_0004: ret
}
// end of method Program::Test1
.method
private
hidebysig
static
void
Test2 (
int32& a
) cil managed
{
// Method begins at RVA 0x2059
// Code size 5 (0x5)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.1
IL_0003: stind.i4
IL_0004: ret
}
// end of method Program::Test2
|
发现了吗? 它们在函数内部完全是一样的!因为他们的原理都是传入了这个变量的引用。只是 out 关键字前面出现了一个标记 [out]
它们在原理上的区别主要在于编译器对它们进行了一定的限制。
最上面“代码段一”中的问题你现在明白了吗?
对于值类型来说,最难区别的是 ref 和 out,而对于引用类型来说就不同了。
首先,引用类型传的是引用,加了 ref 以后也是引用,所以它们是一样的?暂时我们就这么认为吧~ 我们暂时认为它们是一样的,并统称为:传引用。
所以,对于引用类型来说,out 和 传引用 的区别跟对于值类型传 ref 和 out 的区别类似,具体适用场景也和值类型类似,所以就不多加阐述了。
虽然我们说直接传和加 ref 都可以统称为传引用,但是它们还是有区别的!而且是一个很隐蔽的区别。
我们再来看一下最上面的代码段二:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
static
void
Main(
string
[] args)
{
object
a =
new
object
(), b =
new
object
(), c =
new
object
();
Test1(
out
a);
Test2(
ref
b);
Test3(c);
//最终 a,b,c 分别是什么?
//a,b = null
//c 还是 object
}
static
void
Test1(
out
object
a)
{
a =
null
;
}
static
void
Test2(
ref
object
b)
{
b =
null
;
}
static
void
Test3(
object
c)
{
c =
null
;
}
|
out 关键字就相当于 return ,所以内部赋值为 null ,就相当于 return 了 null
可是,为什么引用类型还要加 ref 呢?它本身部已经是引用了吗?为什么加了以后就会有天大的区别呢?!
用一句话概括就是:不加 ref 的引用是堆引用,而加了 ref 后就是栈引用! @_@ 好搞啊。。什么跟什么?让我们一步步说清楚吧!
正常的传递引用类型:
加了 ref 的传递引用类型:
这两张图对于上面那句话的解释很清楚了吧?
如果直接传,只是分配了一个新的栈空间,存放着同一个地址,指向同一个对象。
内外指向的都是同一个对象,所以对 对象内部的操作 都是同步的。
但是,如果把函数内部的 obj2 赋值了 null,只是修改了 obj2 的引用,而 obj1 依然是引用了原来的对象。
所以上面的例子中,外部的变量并没有收到影响。
同样,如果内部的对象作了 obj2 = new object() 操作以后,也不会对外部的对象产生任何影响!
而加了 ref 后,传入的不是 object 地址,传入的是 object 地址的地址!
所以,当你对 obj2 赋 null 值的时候,其实是修改了 obj1 的地址,而自身的地址没变,还是引用到了 obj1
虽然在函数内部的语句是一样的,其实内部机制完全不同。我们可以看一下IL代码,一看就知道了!