CLR默认所有函数参数默认传值,在C#中无论是值类型还是引用类型的参数都是如此。对于值类型是传递了一个副本。
sealed class Program
{
static void Main(string[] args)
{
Point p = new Point();
Console.WriteLine(p.ToString());
AddPoint(p);
Console.WriteLine(p.ToString());
}
public static void AddPoint(Point p)
{
p.x += 1;
p.y += 1;
}
public struct Point
{
public int x, y;
public override string ToString() => $"({x}, {y})";
}
}
上面的代码会输出两个(0,0),因为修改的是传入的副本的值而没有修改对象p本身。
对于引用类型是传递了一个对象的引用,这意味着我们无法改变作为参数传入的那个变量指向的对象。
sealed class Program
{
static void Main(string[] args)
{
Point p = new Point();
Console.WriteLine(p.ToString());
AddPoint(p);
Console.WriteLine(p.ToString());
}
public static void AddPoint(Point p1)
{
p1 = new Point();
p1.x = 1;
p1.y = 1;
}
public class Point
{
public int x, y;
public override string ToString() => $"({x}, {y})";
}
}
上面的代码会输出两个(0,0),因为只是修改p1指向的对象,而p指向的对象没有改变。
而通过使用ref
和out
关键字,我们可以改变上述默认行为,实现参数的引用传递。CLR中是不区分out和ref,无论使用哪个关键字都会生成相同的IL代码。但是C#编译器在处理ref
和out
时会进行一些额外的检查,以确保参数在使用前被正确赋值。
sealed class Program
{
static void Main(string[] args)
{
Point p = new Point();
Console.WriteLine(p.ToString());
AddPoint(ref p);
Console.WriteLine(p.ToString());
}
public static void AddPoint(ref Point p1)
{
p1 = new Point();
p1.x = 1;
p1.y = 1;
}
public class Point
{
public int x, y;
public override string ToString() => $"({x}, {y})";
}
}
上图代码会输出(0,0),(1,1),就是因为ref关键字使得传入了p的引用,因此AddPoint中修改了p1指向的对象也就修改了p指向的对象。
ref和out都能传引用,但是它们有如下的不同:
- ref需要传入一个已经初始化过的对象。
- out需要对象在函数内部有被写入过。