CodeGym /课程 /C# SELF /参数修饰符 ( ref、 out、 in)

参数修饰符 ( ref、 out、 in)

C# SELF
第 12 级, 课程 4
可用

1. 入门

在C#里,方法的所有参数默认都是按值传递,也就是说传进去的是副本,不是原件。

那如果你想在方法里改外面的变量怎么办?或者想从方法里一次性返回多个值?又或者你想把一个很大的结构体传进方法,但又不想复制整个“大箱子”,只想传个引用进去省点内存?

这些都和参数修饰符有关。它们让你能控制数据在调用方和方法之间“怎么走”。

修饰符种类

修饰符 数据传递方向 调用前要初始化吗? 方法里能读吗? 方法里能写吗? 常见使用场景
ref 双向 是 是 是 传递变量用于读和/或修改
out 只往外 否 否(赋值前) 是(必须!) 从方法返回多个值
in 只往里 是 是 否 传递大结构体只读省内存

别担心,咱们马上一个个过一遍。其实比你想的简单多了。

2. ref修饰符:按引用传递,能读也能写

想象一下:你给朋友端了一杯咖啡,说——“来,随便喝,想加热就加热,想加点啥都行”。朋友可以对这杯咖啡为所欲为,最后剩下啥(或者啥都不剩)——都会还给你。ref就是这么干的。


void DoSomething(ref int x)
{
    x = x + 10; // 修改传入参数
}
        
带 ref参数的方法

要用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参数的方法
  • 调用前:变量可以不初始化,甚至可以在调用里直接写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参数的方法(只读)
  • 调用前:变量必须初始化。
  • 方法里:只能读,不能改。
  • 省内存:结构体不会被复制,是按引用传递的。

比如处理大结构体数组时可以用,避免多余的复制。但对类(引用类型)来说,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参数能有几个?
想加多少都行,没限制!不过别太多,代码可读性会变差:如果你有两三个以上,建议考虑用元组、结构体或者类来返回。

2
任务
C# SELF, 第 12 级, 课程 4
已锁定
使用 ref 修饰符
使用 ref 修饰符
2
任务
C# SELF, 第 12 级, 课程 4
已锁定
使用 out 修饰符
使用 out 修饰符
1
调查/小测验
类和对象第 12 级,课程 4
不可用
类和对象
继承入门
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION