C#进阶 值传递和引用传递
在 C# 中,传递参数的方式取决于参数的类型,而不是传递方式的关键字(虽然 ref/out/in可以改变行为)。理解值类型和引用类型的内存布局是掌握参数传递的关键。
1. 核心概念
值类型
- 结构体(`struct`)、枚举(`enum`)、基本类型(`int`, `bool`, `double` 等)
- 直接存储数据
- 分配在栈上(除非被装箱或作为类的字段)
引用类型
- 类(`class`)、接口(`interface`)、委托(`delegate`)、数组(`array`)
- 存储对数据的引用(内存地址)
- 实例分配在堆上
2. 参数传递的四种方式
方式 1:默认传递(按值传递)
// 值类型:传递副本
void ModifyValue(int x)
{
x = 100; // 只修改副本
}
// 引用类型:传递引用的副本
void ModifyArray(int[] arr)
{
arr[0] = 100; // 修改原始对象的内容 ✓
arr = new int[10]; // 只修改局部副本的引用 ✗
}
// 使用
int num = 10;
ModifyValue(num); // num 仍然是 10
int[] array = { 1, 2, 3 };
ModifyArray(array); // array[0] 变为 100,但 array 仍指向原数组方式 2:ref 传递(按引用传递)
// 传递变量本身的引用
void ModifyWithRef(ref int x, ref int[] arr)
{
x = 100; // 修改原始变量
arr = new int[10]; // 修改原始引用
}
// 使用
int num = 10;
int[] array = { 1, 2, 3 };
ModifyWithRef(ref num, ref array);
// num = 100, array 指向新数组方式 3:out 传递(输出参数)
// 用于输出结果,调用前不需要初始化
bool TryParse(string input, out int result)
{
if (int.TryParse(input, out int temp))
{
result = temp;
return true;
}
result = 0;
return false;
}
// 使用(C# 7.0+ 可以在调用时声明变量)
TryParse("123", out int value);方式 4:in 传递(只读引用)
// 传递只读引用,提高大值类型的性能
double CalculateDistance(in Vector3D v1, in Vector3D v2)
{
// v1.X = 10; // 错误:无法修改只读变量
return Math.Sqrt(v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z);
}
readonly struct Vector3D
{
public double X { get; }
public double Y { get; }
public double Z { get; }
}3. 详细对比
| 特性 | 默认(值传递) | ref | out | in |
| 是否需要初始化 | 是 | 是 | 否 | 是 |
| 方法内能否读取 | 是 | 是 | 否(必须赋值) | 是 |
| 方法内能否修改 | 修改副本 | 修改原始 | 必须修改原始 | 不能修改 |
| 值类型性能 | 复制整个值 | 高效(传地址) | 高效(传地址) | 高效(传地址) |
| 调用时语法 | Method(arg) | Method(ref arg) | Method(out arg) | Method(in arg) |
4.最佳实践
1. 避免修改输入参数(除非使用 ref/out 明确意图)
2. 大值类型使用 in 避免复制开销
3. out 用于返回多个值,替代 Tuple
4. 谨慎使用ref,会降低代码可读性
5. readonly struct 与 in参数是性能优化组合

