在C#中,ref、out和in是三种不同的参数传递方式,并且都是引用传递。
引用传递:表示传递的是对象的引用,而不是对象本身。
ref
1)参数在传入方法前必须初始化
2)方法内可以读取和修改参数值,修改会影响原始变量
public void Main()
{
int number = 10;
Debug.Log($"初始值: {number}");
ModifyNumber(number);
Debug.Log($"调用Common ModifyNumber后: {number}");//10
ModifyNumber(ref number);
Debug.Log($"调用ref ModifyNumber后: {number}");//20
ModifyNumber(number);
Debug.Log($"调用Common ModifyNumber后: {number}");//20
}
void ModifyNumber(ref int value)
{
Debug.Log($"调用ref ModifyNumber前: {value}");//10
value *= 2;
}
void ModifyNumber(int value)
{
Debug.Log($"调用Common ModifyNumber前: {value}");//10、20
value *= 3;
}

out
1)参数在传入方法前不需要初始化
2)方法内必须为参数赋值
3)常用于从方法返回多个值
// 需求:我们想从一个方法同时获取学生的姓名和年龄
public class Example_out
{
public void Main()
{
// ✓ 使用 out 可以同时获取多个值
GetStudentInfo(1001, out string name, out int age);
Debug.Log($"学生姓名:{name},年龄:{age}");
// ❌ 如果不用 out,我们就需要多次调用方法
string name2 = GetStudentName(1001);
int age2 = GetStudentAge(1001);
}
// 使用 out 一次性返回多个值
void GetStudentInfo(int id, out string name, out int age)
{
// 模拟从数据库获取数据
name = "张三"; // out 参数必须在方法内赋值
age = 18; // out 参数必须在方法内赋值
}
// 不使用 out 就需要写多个方法
string GetStudentName(int id) { return "张三"; }
int GetStudentAge(int id) { return 18; }
}
out与ref的对比
//out&ref对比
public void Main()
{
// 1. ref 必须先初始化变量
int x = 0; // ref 需要初始化
MethodWithRef(ref x); // 必须用已初始化的变量
// 2. out 不需要初始化
MethodWithOut(out int y); // 直接声明变量
// 或者
int z;
MethodWithOut(out z); // 使用未初始化的变量也可以
}
void MethodWithRef(ref int number)
{
// ref 参数可以不赋值
if (number > 0)
{
number = 100;
}
}
void MethodWithOut(out int number)
{
// out 参数必须赋值
number = 100; // 如果不赋值会编译错误
}
多个返回值Tuple与out的对比
C#也可以使用元组来处理多个返回值。
学习out的时候,忽然想到了,就顺手记一下~
//处理多个返回值的2种方案
public class Example
{
public void Main()
{
// 1. 使用 out 参数
GetPersonInfoWithOut("John", out string title, out int age);
Debug.Log($"Out方式: {title}, {age}");
// 2. 使用元组
var (tupleTitle, tupleAge) = GetPersonInfoWithTuple("John");
Debug.Log($"元组方式: {tupleTitle}, {tupleAge}");
// 2.1 也可以直接使用元组
var person = GetPersonInfoWithTuple("John");
Debug.Log($"元组方式: {person.title}, {person.age}");
}
// 使用 out 参数的方法
void GetPersonInfoWithOut(string name, out string title, out int age)
{
title = "Mr.";
age = 30;
}
// 使用元组的方法
(string title, int age) GetPersonInfoWithTuple(string name)
{
return ("Mr.", 30);
}
}
in
C#7.2引入的新特性
1)用于传递只读引用
2)参数在方法内不能被修改
3)主要用于提高性能,特别是处理大型结构体时
//只读、可传递给其他in参数
public void ProcessData(in Point point)
{
// ❌ 不能直接修改参数
// point.X = 100; // 编译错误
// ✓ 可以读取参数
Console.WriteLine($"X: {point.X}, Y: {point.Y}");
// ❌ 不能传递给非 in 参数
// ModifyPoint(point); // 编译错误
// ✓ 可以传递给其他 in 参数
PrintPoint(in point);
}
//使用场景
// ✓ 适合使用 in 的场景:大型结构体
public struct BigStruct
{
public double Field1;
public double Field2;
// ... 更多字段
}
public double ProcessBigStruct(in BigStruct data)
{
return data.Field1 + data.Field2;
}
// ❌ 不适合使用 in 的场景:小型结构体或引用类型
public void ProcessSmallStruct(in int number) { } // 不必要
public void ProcessString(in string text) { } // 不必要
对于“传递”的深入理解
“ ref、out、in这些关键字确实都是按引用传递,但它们传递的是变量的内存地址引用,而不是创建新的变量。”
这句话中“传递的是变量的内存地址引用”怎么理解呢?
值传递:
void Method1(int x) { x = 20; // 修改的是副本 } int a = 10; Method1(a); // a 还是 10内存示意:
栈内存: a (地址0x1000) → 值: 10 方法内的 x (地址0x2000) → 值: 20 // 复制了一份,在新地址
引用传递:
void Method2(ref int x) { x = 20; // 修改原始变量 } int a = 10; Method2(ref a); // a 变成 20内存示意:
栈内存: a (地址0x1000) → 值: 10 → 20 方法内的 x → 指向地址0x1000 // x 获得了 a 的地址,而不是值的副本
对引用类型的引用传递:
class MyClass { public int Value; } void Method3(ref MyClass obj) { obj = new MyClass(); // 改变引用本身 obj.Value = 20; // 修改新对象的值 } MyClass mc = new MyClass(); mc.Value = 10; Method3(ref mc); // mc 指向新对象内存示意:
栈内存: mc (地址0x1000) → 指向堆地址0xA000 方法内的 obj → 指向地址0x1000 // obj 获得了 mc 的地址 堆内存: 0xA000 → { Value: 10 } // 原始对象 0xB000 → { Value: 20 } // 新对象
对比不使用 ref 的引用类型传递:
void Method4(MyClass obj) { obj = new MyClass(); // 只改变局部引用 obj.Value = 20; // 修改新对象的值 } MyClass mc = new MyClass(); mc.Value = 10; Method4(mc); // mc.Value 还是 10内存示意:
栈内存: mc (地址0x1000) → 指向堆地址0xA000 方法内的 obj (地址0x2000) → 最初指向0xA000,后指向0xB000 // 复制了引用 堆内存: 0xA000 → { Value: 10 } // 原始对象 0xB000 → { Value: 20 } // 新对象(方法结束后被垃圾回收)
总结
初始化要求:
1)ref:必须初始化
2)out:不需要初始化
3)in:必须初始化
方法内部行为:
1)ref:可读可写
2)out:必须写(赋值)
3)in:只读
使用场景:
1)ref:需要在方法内修改参数值时
2)out:需要返回多个值时
3)in:传递大型结构体提升性能时
本文详细解释了C#中的ref、out和in三种参数传递方式的区别,包括引用传递、值传递的特点,以及ref和out在方法中的行为差异。

3437

被折叠的 条评论
为什么被折叠?



