说明问题之前,我们先来看一段代码:
class Program
{
static void Main(string[] args)
{
int a = 1, b = 2;
swap(a, b);
Console.WriteLine("a={0},b={1}", a, b);
}
static void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
}
结果依然是a=1,b=2,swap方法并没有将交换后的结果返回。
这是因为swap方法的形参是按传值形式被调用的。当swap方法准备执行时,形参b和a会被各自赋值(即2和1),然后压入堆栈中,swap方法执行完成后,形参a和b弹栈。这个过程中,Main中的a和b的值始终没有发生变化,所以输出时,依然是原始值。
在java中,我们只能通过return将修改后的值进行返回。但是在C#中,提供了更多的手段,可以不通过return而直接在调用方法中将值修改并影响原来的变量,这就是ref或者out关键字。
class Program
{
static void Main(string[] args)
{
int a = 1, b = 2;
swap(ref a, ref b);
Console.WriteLine("a={0},b={1}", a, b);
}
static void swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
}
这里可以看到,a和b的值已经发生了交换。
那么ref和out的区别在哪里呢?
- ref的参数在调用之前必须先初始化,而out的不需要;
- 在调用方法内,ref的形参可以直接使用,而out的必须先赋值再使用。
比如下面两种错误:
错误一:
其中display方法:
错误二:
总结:
- ref和out提供了按引用传递的功能(这里的“引用”并不是指“引用类型”的引用),我们可以在调用方法内部对传递进来的参数进行修改;
- ref的使用场景:调用方法对参数param进行处理时,依赖param的初始值。比如,方法内部进行param++操作时;
- out的使用场景:调用方法对参数param进行处理时,不依赖param的初始值。比如,方法内部进行param=40这样的赋值操作时。
接下来看一看IL是怎么对待按值或者按引用传递的参数。比如这一段C#代码:
classClass
{
void Method(Class @class) { }
void Method(ref Class @class) { }
//void Method(out Class @class) { }
}
这一段代码是可以正常通过编译的,但是取消注释就不行了,因为IL是不区分ref和out的。
也正是因为这一种重载的可能性,所以在调用方也必须写明ref或out,不然编译器没法区分调用的是哪一个重载版本。
Class类的IL是这样的:
.classprivate auto ansi beforefieldinit CsConsole.Class
extends[mscorlib]System.Object
{
//Methods
.method private hidebysig static
void Method (
class CsConsole.Class'class'
) cil managed
{
//Method begins at RVA 0x20b4
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} //end of method Class::Method
.method private hidebysig static
void Method (
class CsConsole.Class&