c#ref和out的相同点_c#中out和ref有区别么?

本文详细介绍了C#中ref和out参数的差异和使用场景。ref参数用于确保函数内部和调用方使用的是同一块内存,而out参数则主要用于函数返回多个值。C#的ref和out参数在功能上类似于C语言中的指针,但它们在编译时有严格的检查,不能混用。此外,ref和out不能作为方法重载的依据,因为它们在底层都被转换为指针。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本质上都是指针。

Part 1 ref 参数

考虑一种情况。如果 C 语言里,我们需要让数值发生交换,如果单纯写函数是不行的,因为大家都知道,C 语言的函数是值传递,也就意味着它会把数据本身拿出来赋值,就会产生一个一模一样的副本。当需要发生交换的时候,如果用函数执行,函数最终只交换了副本,因此原本的数据并没有变动。所以,你必须得用指针来表示“函数内的变量用的是和原本数据一样的变量”:

void swap(int *a, int *b)

{

int temp = *a;

*a = *b;

*b = temp;

}

我们借用指针才能使得数据成功交换。那么调用方当然就是传入两个地址进去了:

// ...int a = 3, b = 4;

swap(&a, &b);

// ...

这样一波操作后,a 和 b 就可以交换成功了。因为传入的是地址,通过 C 语言的指针间接运算符 * 可以得到内部的数据。那么,如果我们把这个写法类比到 C# 里,那么这种情况就被称为 ref 参数:

void Swap(ref T a, ref T b)

{

var temp = a;

a = b;

b = temp;

}

// 调用方// ...int a = 3, b = 4;

Swap(ref a, ref b);

// ...

所以 ref 本质用于传入一个调用方数据和函数内执行的数据是一个东西的模型。

Part 2 out 参数

接着考虑 out 参数。我们依然从 C 语言角度入手。试想我们要计算一个人的平均分,并返回这个人是否通过考试(平均分大于 60 分)。这个模型可以考虑返回一个 bool 的类型变量。所以写法可以是这样的:

// ↓ 布尔类型(C99 起可用),也可以写别名 bool。_Bool isPassed(float chinese, float english, float math)

{

float total = chinese + english + math;

return total / 3 >= 60;

}

可以从这个函数里看到,这个函数直接执行求和,并返回了平均数是否大于 60 分的行为。那么问题来了。如果我想让这个函数同时也反馈给调用方“这个人平均分是多少”。这个怎么办呢?相信你的第一反应是改写函数的返回值类型。但是实际上这样是不好的,因为我们这个函数目的是求“是不是过了”,而不是“返回多少分”。那么,我们可以考虑从函数内部反馈一个结果出来,从参数传出:

_Bool isPassed(float chinese, float english, float math, float *avg)

{

float total = chinese + english + math;

*avg = total / 3; // 注意这一行。 return *avg >= 60;

}

我们尝试从函数内部为这个指针变量赋值。在调用方的时候可以这么写:

// ...float f; // 一个不用赋值的变量。if (isPassed(70, 80, 90, &f))

{

printf("这个人考试过了!\n");

}

else

{

printf("平均分都没及格,等着重修吧!\n");

}

// ...

那么从外部调用的时候,我们的变量是可以不用赋值的。与其说不用赋值,还不如说成是“赋值没有意义,反正最终都会在这个函数内部执行的过程之中被替换掉”。

这种传参模型就是 out 模式了。写成 C# 便是

// 方法bool IsPassed(float chinese, float english, float math, out float avg)

{

float total = chinese + english + math;

avg = total / 3; // 注意这一行。 return avg >= 60;

}

// 调用方// ...if (IsPassed(70, 80, 90, out float f))

{

Console.WriteLine("过了!");

}

else

{

Console.WriteLine("没过!");

}

// ...

本质上两种传参都是用的指针。但这两个模型的用途和场景不同,所以在 C# 里,编译器可以区分它们。如果返回数值的语句都写了,却没有为 out 参数赋值,那么必然编译器会报错。实际上,out 参数想解决的问题是返回多个数值。显然一个函数只能返回一个对象,虽然后来我们有了 Tuple 泛型和 ValueTuple 泛型可以允许对象返回多个数,也可以用数组来达到这一点,但是老实说,它们都不是最优的解决方案,因为一来是会产生新的对象的内存分配,二来是破坏了代码结构和执行逻辑,毕竟,我们需要的是“这个人是不是过了”。当然,你也不必去咬文嚼字。C# 7 里有值元组的解构,解构的方法用的是多个 out参数,且没有返回值。这会儿并不是为了照顾和强调“返回多个值”,而是想告诉你,因为返回值占一个返回的位置,其它地方只能从参数返回,那么代码看起来就太丑了。所以干脆为了代码的统一,一并把解构的字段都放在参数上。

Part 3 重载

按理说,这个不属于它们的区别。但是前文不是说了那么多知识点吗,这里就顺带提一下。既然它们都被翻译成指针,那么,你怎么区分指针的类型不一样呢?

换言之,如果一个方法传入的参数类型都一样,但只是 ref和 out用得不一样,那么它们构成重载吗?

static void Method(ref int a);

static void Method(out int a);

你可以实践一下,答案是,报错。都被翻译成 int* 了,还哪里能区分得了它们鸭。所以它们不构成重载。但是,不带 ref或 out关键字的方法可以和它们其一构成重载:

static void Method(ref int a);

static void Method(int a);

static void Method2(out int a);

static void Method2(int a);

这样是 OK 的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值