一、ref 和 out 关键字
ref 关键字 是作用是把一个变量的引用传入函数,和 C/C++ 中的指针几乎一样,就是传入了这个变量的栈指针。
out 关键字 的作用是当你需要返回多个变量的时候,可以把一个变量加上 out 关键字,并在函数内对它赋值,以实现返回多个变量。
out 关键字在传入前可以不赋值,ref 关键字在传入前一定要赋值。
static void Main(string[] args)
{
int a;
Test1(out a);//编译通过
int b;
Test2(ref b);//编译失败
}
static void Test1(out int a)
{
a = 1;
}
static void Test2(ref int b)
{
b = 1;
}
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;
}
1.对于值类型
如果你是为了能多返回一个变量,那么就应该用 out:
用 out 关键字有几个好处:可以不关心函数外是否被赋值,并且如果在函数内没有赋值的话就会编译不通过。(提醒你一定要返回)
你可以把它当成是另一种形式的 return 来用,我们来做一个类比:
return 语句的特点:接收 return 的变量事先不需要赋值(当然如果赋值了也没关系),在函数内必须 return。
可以看到 out 关键字的作用、行为、特点 和 return 是完全一样的。因为它当初设计的目的就是为了解决这个问题的。
如果你想要像引用类型那样调用值类型,那你就可以 ref:
传入值类型的引用后,你可以用它,也可以不用它,你也可以重新修改它的各个属性,而函数外也可以随之改变。
我们来把 “传值类型的引用” 和 “传引用类型” 来做一个类比:
static void Main(string[] args)
{
int a;
Test1(ref a);//错误 1 使用了未赋值的局部变量“a”
object b;
Test2(b);//错误 2 使用了未赋值的局部变量“b”
}
static void Test1(ref int a) { }
static void Test2(object b) { }
传入加了 ref 的值类型 和 传入一个引用类型 的作用、行为、特点都是类似的。
同样,他们同时要遵守一个原则:传入前必须赋值,这个是为什么呢?
如果赋值后,传入两个函数的分别是 int a 的指针 和 object b 的指针。
而不赋值的话,a 和 b 根本还不存在,那它们又怎么会有地址呢?
注意:如果你只写了 object a ,而在后面的代码中没有赋值,它并没有真正地分配内存。
private static void Main(string[] args)
{
object a;//没做任何事
object b = null;//在栈中增加了一个指针,指向 null
object c = new object();//在栈中增加了一个指针,指向新建的 object 对象
}
传入引用类型的目的是把一个已经存在的对象的地址传过去,而如果你只是进行了 object a 声明,并没做复制,这行代码跟没做任何事!
所以,除非你使用了 out 关键字,在不用关键字和用 ref 关键字的情况下,你都必须事先复制。 out 只是一种特殊的 return。
private static void Test1(out int a)
{
a = 1;
}
private static void Test2(ref int a)
{
a = 1;
}
它们在函数内部完全是一样的,因为他们的原理都是传入了这个变量的引用。
2.对于引用类型
首先,引用类型传的是引用,加了 ref 以后也是引用,暂时认为它们是一样的,并统称为:传引用。
所以,对于引用类型来说,out 和 传引用 的区别跟对于值类型传 ref 和 out 的区别类似,具体适用场景也和值类型类似,所以就不多加阐述了。
虽然我们说直接传和加 ref 都可以统称为传引用,但是它们还是有区别的!
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;
}
为什么引用类型还要加 ref 呢?它本身不就已经是引用了吗?
不加 ref 的引用是堆引用,而加了 ref 后就是栈引用!
如果直接传,只是分配了一个新的栈空间,存放着同一个地址,指向同一个对象。
内外指向的都是同一个对象,所以对 对象内部的操作 都是同步的。
但是,如果把函数内部的 obj2 赋值了 null,只是修改了 obj2 的引用,而 obj1 依然是引用了原来的对象。
所以上面的例子中,外部的变量并没有收到影响。
同样,如果内部的对象作了 obj2 = new object() 操作以后,也不会对外部的对象产生任何影响!
而加了 ref 后,传入的不是 object 地址,传入的是 object 地址的地址!
所以,当你对 obj2 赋 null 值的时候,其实是修改了 obj1 的地址,而自身的地址没变,还是引用到了 obj1
虽然在函数内部的语句是一样的,其实内部机制完全不同。
再看了一个例子:
private static void Test1(List<int> list)
{
list.Clear();
}
private static void Test2(ref List<int> list)
{
list = new List<int>();
}
同样是清空一个 List,如果没加 ref ,只能用 clear。
而加了 ref 后可以直接 new 一个新的~
如果你没加 ref 就直接 new 一个新的了,外部根本不知道有这个东西,你们操作的将不是同一个 List
所以,你一定要了解这点,并注意一下几件事:
1、一般情况下不要用 ref
2、如果你没加 ref,千万别直接给它赋值,因为外面会接收不到