ref
通常我们向方法中传递的是值,方法获得的是这些值的一个拷贝,然后使用这些拷贝,当方法运行完毕后,这些拷贝将被丢弃,而原来的值不会受到影响。 这种情况是通常的,当然还有另外一种情况,我们向方法传递参数的形式,引用(ref)和输出(out)。
有时,我们需要改变原来变量中的值,这是我们可以向方法传递变量引用,而不是变量的值,引用是一个变量,他可以访问原来变量的值,修改引用将修改原来变量的值。变量的值存储在内存中,可以创建一个引用,他指向变量在内存中的位置,当引用被修改时,修改的是内存中的值,因此变量的值可以被修改,当我们调用一个含有引用参数的方法时,方法中的参数将指向被传递给方法的相应变量,因此,我们会明白为什么当修改参数变量的修改也将导致原来变量的值被修改。
out
通过制定返回类型,可以从方法返回一个值,有时候,需要返回多个值,虽然我们可以使用ref来完成,但是C#专门提供了一个属性类型,关键字为out。
ref和out似乎可以实现相同的功能,因为都可以改变传递到方法中的变量的值,但是二者本质的区别就是,ref是传入值,out是传出值,在含有out关键之的方法中,变量 必须有方法参数中不含out(可以是ref)的变量赋值或者由全局(即方法可以使用的该方法外部变量)变量赋值,out的宗旨是保证每一个传出变量都必须被赋值
在传入变量的时候,out关键字的变量可以不被初始化,但是没有out 关键字的值要被赋值。而ref参数在传递给方法是,就已经被赋值了,所以ref侧重修改,out侧重输出。
现在来看一下demo:
这对值类型:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace out_ref_demo
{
class Program
{
static void Main(string[] args)
{
int a = 5;
add1(a);//输出
//add2(ref a);
//add3(out a);
Console.WriteLine("main:" + a);
Console.ReadKey();
}
public static void add1(int a)
{
Console.WriteLine("内部改写前 add1:" + a);
//直接将变量值带入函数中
a = 10;
Console.WriteLine("内部改写后 add1:" + a);
}
public static void add2(ref int a)
{
Console.WriteLine("内部改写前 add2:" + a);
//即会将变量值带入函数中,又会改写a的值
a = 10;
Console.WriteLine("内部改写后 add2:" + a);
}
public static void add3(out int a)
{
// Console.WriteLine("before add3:" + a);
//改写a的值,但是不会把a的值带入函数中
a = 10;
Console.WriteLine("内部改写后 add3:" + a);
}
}
}
首先,out和ref的区别就很明显了
out :改写a的值,但是不会把a的值带入函数中,也就是说外面传进来的参数a虽然给a提前赋过值了,但是进入add3函数的时候并没有携带进来,所以在没有给a赋值之前直接Console.WriteLine("before add3:" + a);就会报错:
所以out不能将值带入函数,但是确可以将值带出函数。
而ref就可以将值带入函数,因为在给a赋值之前Console.WriteLine("before add2:" + a);并不会报错,并且a的值是5,而且还能将值带出来,也即可以带入值,并且函数里该写后还能将改写后的值带出。
对于普通参数add1函数而言,在函数内更改a的值后确不能将值带出。
下面是三个函数的分别打印结果,一目了然:
对于add1:
对于add2:
对于add3:
对象类型:
public class ClassParam
{
public void say1(Person p)
{
Console.WriteLine("内部改写前 say1:" + p.name);
p.name = "xue";
Console.WriteLine("内部改写后 say1:" + p.name);
}
public void say2(ref Person p)
{
Console.WriteLine("内部改写前 say2:" + p.name);
p.name = "xue";
Console.WriteLine("内部改写后 say2:" + p.name);
}
public void say3(out Person p)
{
// Console.WriteLine("内部改写前 say3:" + p.name);
//因为out只出不进,所有要重新new
p = new Person();
p.name = "xue";
Console.WriteLine("内部改写后 say3:" + p.name);
}
}
public class Person
{
public string name;
}
main函数:
static void Main(string[] args)
{
int a = 5;
//add1(a);//输出
//add2(ref a);
//add3(out a);
// Console.WriteLine("main:" + a);
ClassParam cp = new ClassParam();
Person p = new Person();
p.name = "fashi";
// cp.say1(p);
cp.say2(ref p);
//cp.say3(out p);
Console.WriteLine("main:" + p.name);
Console.ReadKey();
}
针对say1:
针对say2:
针对say3:
可以看到对象类型时,普通对象参数能把值带出来,这是为什么呢,这是因为函数内部更改的name的值,实际上是操作栈区中的p所指向的堆区中的name属性对象,所以在函数内部把name改了,就被p带出去了。
从上面结果看,貌似say1和say2没啥区别,实际不然
我们在函数内部对p重新new之后,却别就出来了:
public void say1(Person p)
{
Console.WriteLine("内部改写前 say1:" + p.name);
p = new Person();
p.name = "xue";
Console.WriteLine("内部改写后 say1:" + p.name);
}
public void say2(ref Person p)
{
Console.WriteLine("内部改写前 say2:" + p.name);
p = new Person();
p.name = "xue";
Console.WriteLine("内部改写后 say2:" + p.name);
}
通过在函数内部添加下面这行代码后我们再看结果
p = new Person();
对于say1:
对于say2:
所以对于引用类型,无论是值传递还是引用传递,在函数内部对于对象的修改,都是有效的
参数传递,重点讨论的是:栈中变量的值是以何种传递方式
如果栈中变量是值传递,则内部的修改对于外部无效,如果栈中变量是引用传递,则内部的修改对于外部有效