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需要对象在函数内部有被写入过。