目录
ref
、out
和 in
都是参数修饰符,它们都允许通过引用来传递参数,而不是像默认情况那样通过值来传递。这意味着方法内部对参数的任何操作都会影响到原始变量。
它们三者之间有着关键的区别,主要体现在数据传递的方向和初始化要求上。
1. ref
(Reference - 引用)
ref
关键字用于双向数据传递。方法既可以读取传入的值,也可以修改它。
- 核心思想:有一个已经初始化的变量,想让方法看到它的当前值,并且允许方法修改它。(变量进门要先初始化,方法内外同步修改,像极了一边提需求一边监工的老板。)
- 规则:
- 调用前必须初始化:传递给
ref
参数的变量在调用方法之前必须被赋值。 - 方法内可以修改:方法内部可以读取和修改这个变量。
- 方法内不是必须修改:方法可以选择不修改它。
- 调用前必须初始化:传递给
示例:
一个经典的例子是交换两个变量的值。
public void RefExample()
{
int a = 5;
int b = 10;
Console.WriteLine($"Before: a = {a}, b = {b}"); // 输出: Before: a = 5, b = 10
// 必须使用 ref 关键字来调用
Swap(ref a, ref b);
Console.WriteLine($"After: a = {a}, b = {b}"); // 输出: After: a = 10, b = 5
}
// 方法定义时也必须使用 ref
void Swap(ref int x, ref int y)
{
// 方法内可以读取 x 和 y 的值,并修改它们
int temp = x;
x = y;
y = temp;
}
2. out
(Output - 输出)
out
关键字用于单向数据传递,方向是从方法内传出到方法外。它通常用于一个方法需要返回多个值的场景。
- 核心思想:需要方法给我一个值,不需要提前准备好这个值,但必须在方法结束前给我赋一个值。(变量可以空手进门,但方法内必须赋值,否则编译器会拿小本本记仇 。)
- 规则:
- 调用前无需初始化:传递给
out
参数的变量在调用前不需要初始化。 - 方法内必须赋值:方法在返回之前必须为
out
参数赋值。 - 方法内不能读取:在为其赋值之前,方法不能读取
out
参数的值。
- 调用前无需初始化:传递给
示例:
最常见的例子是 int.TryParse
,它尝试解析一个字符串,如果成功,就通过 out
参数返回结果。
public void OutExample()
{
string numberString = "123";
int parsedNumber; // 无需初始化: int parsedNumber = 0;
// 调用时必须使用 out 关键字
if (int.TryParse(numberString, out parsedNumber))
{
Console.WriteLine($"Parsing successful: {parsedNumber}"); // 输出: Parsing successful: 123
}
else
{
Console.WriteLine("Parsing failed.");
// 即使失败,TryParse 内部也会给 parsedNumber 赋值 (通常是 0)
}
// parsedNumber 现在是 123
}
// TryParse 的简化版签名
// bool TryParse(string s, out int result) { ... }
3. in
(Input - 输入)
in
关键字(C# 7.2 引入)也通过引用传递,但它是只读的。这主要是为了性能优化,特别是当传递大型结构体(struct)时。
- 核心思想:有一个很大的数据(通常是结构体),想让方法读取它,为了效率不要复制,同时禁止修改这个结构体。(全天候监控的智能摄像头,专门对付那些想偷改数据的「内存刺客」)
- 规则:
- 调用前必须初始化:和
ref
一样,变量在传递前必须被初始化。 - 方法内不能修改:方法内部不能修改
in
参数。任何尝试修改它的行为都会导致编译错误。
- 调用前必须初始化:和
示例:
有一个非常大的结构体,比如一个三维点,计算它到原点的距离。
public readonly struct Point3D // 使用 readonly struct 是个好习惯
{
public double X { get; }
public double Y { get; }
public double Z { get; }
public Point3D(double x, double y, double z)
{
X = x; Y = y; Z = z;
}
}
public void InExample()
{
Point3D myPoint = new Point3D(10, 20, 30); // 必须初始化
// 调用时可以使用 in,也可以省略(但为了清晰建议写上)
double distance = CalculateDistance(in myPoint);
Console.WriteLine($"Distance: {distance}");
}
// 方法定义时使用 in
double CalculateDistance(in Point3D p)
{
// p.X = 0; // 编译错误! 不能修改 in 参数。
// 只能读取 p 的值
return Math.Sqrt(p.X * p.X + p.Y * p.Y + p.Z * p.Z);
}
使用 in
可以避免创建 Point3D
结构体的副本,从而在处理大量或大型结构体时提高性能。
总结对比表
特性 (Feature) | ref | out | in |
---|---|---|---|
主要目的 | 读/写 | 只写 (输出) | 只读 |
数据流向 | 双向 (In/Out) | 单向 (Out) | 单向 (In) |
调用前必须初始化? | 是 | 否 | 是 |
方法内可以修改? | 是 | 是 | 否 (编译错误) |
方法内必须赋值? | 否 | 是 | 否 |
主要用途 | 修改已存在的值 | 返回多个值 | 性能优化 (避免大结构体复制) |