在C#中方法的参数可以通过两种方式传递:值方式传递、引用方式传递。
通过引用方式传递参数,允许函数成员更改参数的值并保持该更改。若要通过引用方式传递,则需使用关键字ref或out。在C#中除非特别说明,否则都是以值方式传递数据。值类型变量直接包含其数据;引用类型变量不直接包含其数据,它包含的是对数据的引用。因此按值方式传递变量意味着向方法传递变量的一个副本,按引用方式传递变量意味着向方法传递变量的引用。
根据参数类型和传递方式的不同,有以下4种情况:
- 值类型参数按值方式传递
- 引用类型参数按值方式传递
- 值类型参数按引用方式传递
- 引用类型参数按引用方式传递
*按引用传递可以用ref修饰,也可以用out修饰,具体区别后面会做介绍。
下面将分别进行说明:
1.值类型参数按值方式传递
值类型参数传递的是该值类型的一个拷贝,被调用方法操作的是属于自己本身的拷贝,因此不影响原来调用方法中的参数值。见如下Demo:
static void Main(string[] args)
{
int num1 = 10;
Add(num1);
Console.WriteLine(num1);
}
static void Add(int num1) { num1 = num1 + 5; Console.Write(num1 + "\t"); }
以上Demo运行的结果为:15 10,可以看出Main()方法中num1的值只传递了一个参数的拷贝,在Add()方法中的改变并没有影响到Main()方法中num1的值,其值仍为10。
2.引用类型参数按值方式传递
static void Main(string[] args) { int[] num = { 1, 3, 5 }; Console.WriteLine("num[0]={0}",num[0]); ChangeTest(num); Console.WriteLine("num[0]={0}",num[0]); Console.WriteLine("num[1]={0}",num[1]); } static void ChangeTest(int[] x) { x[0] = -5; x = new int[] { 2, 4, 6 }; Console.WriteLine("x[0]={0}",x[0]); }
输出结果为:
num[0]=1
x[0]=2
num[0]=-5
num[1]=3
变量num为引用类型,由于采用的是值方式传递,所以将向方法传递指向num的引用的副本。由于num和x 都指向了同一个对象,所以对值的修改会保存,但在方法内部,使用new运算符重新对x分配内存,将使变量x引用新的数组,在这之后所做的更改将不会影响原数组num。
按值方式传递的实质的是传递值,不同的是这个值在值类型和引用类型的表现是不同的:参数为值类型时,“值”为数据本身,因此传递的是数据拷贝,不会对原来的数据产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝(指向同一个对象),这是二者在统一概念上的表现区别,理解了本质也就抓住了根源。
3.值类型参数按引用方式传递
不管是值类型还是引用类型,按引用传递必须以ref或者out关键字来修饰,其规则是:方法定义和方法调用必须同时显示的使用ref或者out,否则将导致编译错误。见如下Demo:
static void Main(string[] args)
{
int num = 5;
Console.WriteLine("num={0}",num);
ChangeTest(ref num);
Console.WriteLine("num={0}",num);
}
static void ChangeTest(ref int x)
{
x *= x;
Console.WriteLine("x={0}",x);
}
输出结果为:
num=5
x=25
num=25
传递的不是num的值,而是num的引用,参数x不是int类型,它是对int(即num)的引用,所以在方法内对x求平方时,实际被求平方的是x所引用的项。
如果仅仅是按引用传递值类型参数的话,那么不会发生装箱和拆箱的问题。也就是说,它并没有产生另外一份数据,而是用指针的方式指向了参数所代表的那份数据而已(这份数据可能在栈上面,也可能在堆上面),但总之是一个指针引用,所以说,按照引用传递的情况,我们如果在ChangeTest中修改了num的值,那么后续访问num这个变量,它的值就确实被改变了。
4.引用类型参数的按引用传递
static void Main(string[] args)
{
int[] num = { 1, 3, 5 };
Console.WriteLine("num[0]={0}", num[0]);
ChangeTest(ref num);
Console.WriteLine("num[0]={0}", num[0]);
Console.WriteLine("num[1]={0}", num[1]);
}
static void ChangeTest(ref int[] x)
{
x[0] = -5;
x = new int[] { 2, 4, 6 };
Console.WriteLine("x[0]={0}", x[0]);
}
输出结果为:
num[0]=1
x[0]=2
num[0]=2
num[1]=4
按引用方式传递时,不管参数是值类型还是引用类型,传递的是参数的地址,也就是实例的指针。ref和out关键字将告诉编译器,方法传递的是参数地址,而不是参数本身。如果参数是引用类型,则按引用传递时,传递的是引用的引用而不是引用本身,类似于指针的指针概念。
总结ref和out的区别
在C# 中,既可以通过值方式也可以通过引用方式传递参数。通过引用方式传递参数允许函数成员更改参数的值,并保持该更改。若要通过引用方式传递参数, 可使用ref或out关键字。ref和out这两个关键字都能够提供相似的功效。它们的区别是:
1.使用ref型参数时,传入的参数必须先被初始化。对out而言,必须在方法中对其完成初始化。
2.out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。