前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
当前回答
还是觉得需要好好总结一下,这就是我想出来的。
总结,
当我们在函数内部时,这就是我们如何指定变量数据访问控制,
in = R
out = W必须在R之前
参考 = R+W
解释,
in
函数只能读取该变量。
out
变量不能先初始化,因为, 函数必须先写后读。
ref
函数可以读/写该变量。
为什么要这样命名呢?
关注数据被修改的地方,
in
数据必须在输入(in)函数之前设置。
out
数据必须只在离开(out)函数之前设置。
ref
在输入函数之前必须设置数据。 在离开(out)函数之前可以设置数据。
其他回答
作为ref传递的参数必须在传递给方法之前初始化,而out形参在传递给方法之前不需要初始化。
使用out表示该参数未被使用,仅被设置。这有助于调用者理解您总是在初始化参数。
此外,ref和out不仅适用于值类型。它们还允许您重置引用类型从方法中引用的对象。
它取决于编译上下文(参见下面的示例)。
out和ref都表示通过引用传递变量,但ref要求变量在传递之前进行初始化,这在封送处理上下文中是一个重要的区别(Interop: UmanagedToManagedTransition或反之亦然)。
MSDN警告说:
不要将按引用传递的概念与引用类型的概念混淆。这两个概念是不一样的。不管方法形参是值类型还是引用类型,都可以通过ref来修改。通过引用传递值类型时,没有对值类型进行装箱。
来自MSDN官方文档:
:
out关键字使参数通过引用传递。这类似于ref关键字,不同的是ref要求在传递变量之前对变量进行初始化
裁判:
ref关键字使参数按引用传递,而不是按值传递。通过引用传递的效果是,对方法中参数的任何更改都反映在调用方法中的底层参数变量中。引用形参的值始终与基础实参变量的值相同。
当参数被赋值时,我们可以验证out和ref确实是相同的:
CIL的例子:
考虑下面的例子
static class outRefTest{
public static int myfunc(int x){x=0; return x; }
public static void myfuncOut(out int x){x=0;}
public static void myfuncRef(ref int x){x=0;}
public static void myfuncRefEmpty(ref int x){}
// Define other methods and classes here
}
在CIL中,myfuncOut和myfuncRef的指令与预期相同。
outRefTest.myfunc:
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: starg.s 00
IL_0004: ldarg.0
IL_0005: stloc.0
IL_0006: br.s IL_0008
IL_0008: ldloc.0
IL_0009: ret
outRefTest.myfuncOut:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.0
IL_0003: stind.i4
IL_0004: ret
outRefTest.myfuncRef:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.0
IL_0003: stind.i4
IL_0004: ret
outRefTest.myfuncRefEmpty:
IL_0000: nop
IL_0001: ret
Nop:不操作,ldloc: load local, stloc: stack local, ldarg: load参数,bs。S:分支到目标....
(见:CIL指令列表)
需要注意的是,in在c# ver 7.2是一个有效的关键字:
The in parameter modifier is available in C# 7.2 and later. Previous versions generate compiler error CS8107 ("Feature 'readonly references' is not available in C# 7.0. Please use language version 7.2 or greater.") To configure the compiler language version, see Select the C# language version. ... The in keyword causes arguments to be passed by reference. It makes the formal parameter an alias for the argument, which must be a variable. In other words, any operation on the parameter is made on the argument. It is like the ref or out keywords, except that in arguments cannot be modified by the called method. Whereas ref arguments may be modified, out arguments must be modified by the called method, and those modifications are observable in the calling context.
如果计划读取和写入参数,则需要使用ref。如果你只打算写,你需要使用out。实际上,当您需要多个返回值时,或者当您不想使用正常的返回机制进行输出时(但这种情况应该很少发生),就会使用out。
有一些语言机制可以帮助这些用例。Ref形参在传递给方法之前必须已经初始化(强调它们是可读可写的),out形参在被赋值之前不能被读取,并且保证在方法结束时已经被写入(强调它们只能被写入)。违反这些原则将导致编译时错误。
int x;
Foo(ref x); // error: x is uninitialized
void Bar(out int x) {} // error: x was not written to
例如,int。TryParse返回bool类型,并接受out int形参:
int value;
if (int.TryParse(numericString, out value))
{
/* numericString was parsed into value, now do stuff */
}
else
{
/* numericString couldn't be parsed */
}
这是需要输出两个值的一个明确示例:数值结果和转换是否成功。CLR的作者决定在这里选择out,因为他们不关心int之前可能是什么。
作为参考,你可以看联锁。增量:
int x = 4;
Interlocked.Increment(ref x);
联锁。Increment自动地增加x的值。因为需要读取x来增加它,所以在这种情况下ref更合适。你完全关心x在传递给Increment之前是什么。
在c#的下一个版本中,甚至可以声明变量In out形参,更加强调它们只输出的性质:
if (int.TryParse(numericString, out int value))
{
// 'value' exists and was declared in the `if` statement
}
else
{
// conversion didn't work, 'value' doesn't exist here
}