1. 入门
在C#里,方法的所有参数默认都是按值传递,也就是说传进去的是副本,不是原件。
那如果你想在方法里改外面的变量怎么办?或者想从方法里一次性返回多个值?又或者你想把一个很大的结构体传进方法,但又不想复制整个“大箱子”,只想传个引用进去省点内存?
这些都和参数修饰符有关。它们让你能控制数据在调用方和方法之间“怎么走”。
修饰符种类
| 修饰符 | 数据传递方向 | 调用前要初始化吗? | 方法里能读吗? | 方法里能写吗? | 常见使用场景 |
|---|---|---|---|---|---|
| ref | 双向 | 是 | 是 | 是 | 传递变量用于读和/或修改 |
| out | 只往外 | 否 | 否(赋值前) | 是(必须!) | 从方法返回多个值 |
| in | 只往里 | 是 | 是 | 否 | 传递大结构体只读省内存 |
别担心,咱们马上一个个过一遍。其实比你想的简单多了。
2. ref修饰符:按引用传递,能读也能写
想象一下:你给朋友端了一杯咖啡,说——“来,随便喝,想加热就加热,想加点啥都行”。朋友可以对这杯咖啡为所欲为,最后剩下啥(或者啥都不剩)——都会还给你。ref就是这么干的。
void DoSomething(ref int x)
{
x = x + 10; // 修改传入参数
}
要用ref传参数,声明和调用方法时都得加这个修饰符:
int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // 输出15
必须是变量,不能把表达式按引用传递。下面这样是不行的:
DoSomething(ref 10); // 这里必须是变量!
- 调用前:变量必须初始化(得有值)。
- 方法里:可以读也可以改。
- 调用后:改动会保留。
例子:交换变量的值
有时候你想把两个变量的值交换一下。但有个坑:C#里的值类型(比如int)默认是按值传递的。也就是说,传进方法的是值的副本,不是变量本身。要让方法能改外面的变量,得用ref关键字。
不用ref的例子——没效果:
// 尝试交换值——没用
void Swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
Swap(x, y); // 传的是值的副本
Console.WriteLine($"{x}, {y}"); // → 10, 20 ——啥都没变!
在Swap函数里,a和b其实是x和y的副本。你只是在改副本,原件没动。
用ref的例子——变量值真的换了
// 用ref交换变量的值
void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
Swap(ref x, ref y); // 传的是变量的引用
Console.WriteLine($"{x}, {y}"); // → 20, 10 ——真的换了
ref让你传递变量本身的引用,而不是它的值。
所以Swap函数直接操作x和y,内容就变了。
什么时候用ref?
- 你想直接改传进来的变量(比如加个数、换个值)。
- 不想用return返回新值,而是直接改参数。
- 别用它来“塞”一堆不同的值——那还是用别的修饰符更合适。
3. out修饰符:只往外传
现在想象一下:你去朋友家,手里拿着空杯子,说:“帮我倒点啥进去,咖啡也行,茶也行!”然后你朋友必须往里倒点啥,不然编译器会发飙。
out就是为了让方法能返回多个不同的值,而不是只用返回值。这样的参数必须在方法里被赋值。
void GetCoordinates(out int x, out int y)
{
x = 5;
y = 10; // 不写这个会报错!
}
- 调用前:变量可以不初始化,甚至可以在调用里直接写out int x。
- 方法里:必须在方法结束前赋值,否则编译器罢工。
- 调用后:变量有新值。
例子:方法一次返回两个值
void ParseNameAndAge(string input, out string name, out int age)
{
string[] parts = input.Split(','); // 把字符串按','分割成数组
name = parts[0];
age = int.Parse(parts[1]);
}
string userInput = "Joe,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} years");
什么时候用out?
- 你想让方法返回多个值(比如把字符串拆成几部分)。
- 返回值类型不确定(比如尝试把字符串转成数字)。
.NET里的例子:方法int.TryParse(string, out int)——经典!
string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
Console.WriteLine($"转换成功: {parsedNumber}");
}
else
{
Console.WriteLine("转换出错!");
}
int.TryParse方法会返回true,如果能把字符串转成数字,false表示失败。数字本身是通过out返回的。
用ref和out时常见的坑和趣事
- 忘了初始化ref变量——编译器会生气。
- 没给out变量赋值——编译器更生气。
- 调用时忘了写修饰符:写成DoSomething(x)而不是DoSomething(ref x)。
- 不能用ref或out修饰常量或字面量!只能用变量。只有变量才有内存地址,才能传引用。
4. in修饰符:只读引用
有时候你得把一个很大很复杂的对象传进方法。通常这种对象是按引用传递的,但这样方法就能随便改它——因为你把引用给它了。
理论上你可以复制一份对象再传,但如果对象很大,这样会白白浪费内存。更好的办法是把对象传进方法,同时让编译器保证没人能改它。
in是个新修饰符,能让你把结构体按引用传递,但只能读不能改。就像“玻璃柜里的宝剑”:只能看,不能摸。
void PrintPoint(in Point pt)
{
pt.X = 5; // 报错! 只能读
Console.WriteLine($"点: {pt.X}, {pt.Y}"); //这样可以
}
- 调用前:变量必须初始化。
- 方法里:只能读,不能改。
- 省内存:结构体不会被复制,是按引用传递的。
比如处理大结构体数组时可以用,避免多余的复制。但对类(引用类型)来说,in没啥特别用处。
例子:按引用传递大结构体,避免复制
struct BigData
{
public int A, B, C, D, E;
}
void PrintBigData(in BigData data)
{
data.A = 10; // 不行——只能读!
Console.WriteLine(data.A + data.B + data.C + data.D + data.E);
}
BigData myData = new BigData { A = 10, B = 20, C = 30, D = 40, E = 50 };
PrintBigData(in myData);
5. 常见问题:
ref/out/in能用在数组和引用类型上吗?
能!不过要记住:数组本身就是引用类型。用ref传数组时,你可以改数组的引用。大多数时候这些修饰符还是用在结构体上更有意义。
ref和out有啥区别?我都能拿到新值啊?
用ref时,变量必须先初始化。用out时不需要,但在方法里必须至少赋值一次。
如果我用字面量(比如数字5)配ref/out/in会怎样?
编译器直接报错。只能用变量!
ref/out/in参数能有几个?
想加多少都行,没限制!不过别太多,代码可读性会变差:如果你有两三个以上,建议考虑用元组、结构体或者类来返回。
GO TO FULL VERSION