有人问我,什么情况下应该使用out
参数关键字而不是ref
。虽然我(我想)理解了ref
和out
关键字之间的区别(在之前已被问过),最好的解释似乎是ref
== in
和out
,但什么情况下我应该始终使用out
而不是ref
呢?请提供一些(假设或代码)示例。
由于ref
更为通用,那么为什么还要使用out
呢?这只是一种语法糖吗?
如果你不需要ref
,应该使用out
。
当数据需要进行编组(例如传递到另一个进程)时,使用out
和使用ref
会有很大的区别,这可能是代价高昂的。因此,当方法不使用初始值时,您应该避免编组初始值。
除此之外,它还可以向声明或调用的读者显示初始值是否相关(并且可能被保留)或被丢弃。
作为一个次要的差异,out
参数不需要初始化。
out
的示例:
string a, b;
person.GetBothNames(out a, out b);
GetBothNames是一个同时检索两个值的方法,无论a和b是什么,该方法都不会改变其行为。如果调用需要到夏威夷的服务器,将初始值从此处复制到夏威夷将浪费带宽。使用ref的类似代码段:
string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);
这可能会使读者困惑,因为它看起来好像a和b的初始值是相关的(尽管方法名表明它们并不相关)。
ref
的示例:
string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);
这里的初始值与方法相关。
ref
关键字。 - C.Evenhuis使用 "out" 关键字表示该参数未被使用,仅被设置。这有助于调用者理解您始终在初始化参数。
另外,“ref”和“out”不仅适用于值类型。它们还允许您在方法内重置引用类型所引用的对象。
out
参数在进入函数时被视为未赋值。在您首先明确分配某个值之前,您将无法检查它们的值 - 没有任何方法可以使用函数调用时参数所具有的值。 - Ben Voigt你说得对,语义上,ref
提供了“输入”和“输出”功能,而 out
只提供了“输出”功能。需要考虑以下几点:
out
要求接受参数的方法必须在返回之前某个时刻为变量赋值。您会在一些键/值数据存储类(如 Dictionary<K,V>
)中找到此模式,其中有像 TryGetValue
这样的函数。该函数将使用 out
参数来保存检索到的值。调用者传递一个值进入这个函数是没有意义的,因此使用 out
来保证在调用后某个时刻变量中将有一些值,即使它不是“真实”的数据(在 TryGetValue
情况下,键不存在)。out
和 ref
参数在处理互操作代码时进行不同的编排。out
和ref
都表示通过引用传递变量,但ref
要求在传递之前对变量进行初始化,这在封送处理的上下文中可能是一个重要的区别(Interop: UmanagedToManagedTransition或反之亦然)。
MSDN warns:out
:out关键字导致参数通过引用传递。这类似于ref关键字,只是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
}
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:加载本地变量,stloc:存储本地变量,ldarg:加载参数,bs.s:跳转到目标....
(参见:CIL指令列表)
如果您是一个视觉化的人,请查看此YouTube视频,该视频演示了实际差异https://www.youtube.com/watch?v=lYdcY5zulXA
下面的图片更直观地显示了差异
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 */
}
out
,因为他们不在意之前可能存在的int
值。ref
,您可以参考Interlocked.Increment
。int x = 4;
Interlocked.Increment(ref x);
Interlocked.Increment
原子地增加x
的值。由于需要读取x
才能将其增加,这种情况下使用ref
更为适合。你完全关心在传递给Increment
之前x
的值是多少。
在C#的下一个版本中,甚至可以在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
}
out
参数并没有被初始化,所以编译器不会让你在向其写入内容之前读取它。 - zneaknameOut
赋值,所以你无法读取它。 - zneak如何在C#中使用in
或out
或ref
?
C#
中的所有关键字都具有相同的功能,但存在一些限制。in
参数不能被调用方法修改。ref
参数可以被修改。ref
必须在被调用前被初始化,它可以在方法中被读取和更新。out
参数必须由调用者进行修改。out
参数必须在方法中进行初始化。in
参数传递的变量在传递到方法调用之前必须进行初始化。但是,被调用的方法可能不会分配值或修改该参数。您不能将in
、ref
和out
关键字用于以下类型的方法:
async
修饰符定义的方法。yield return
或yield break
语句。如果你还需要一个好的总结,这就是我想到的。
当我们在函数内部时,这是如何指定变量数据访问控制的:
in
= R
out
= 必须先写入再读取
ref
= R+W
in
函数只能读取该变量。
out
变量不能先初始化,因为函数必须先写入它,然后才能读取它。
ref
函数可以对该变量进行读/写操作。
关注数据何时被修改:
in
数据必须在进入(“in”)函数之前设置。
out
数据必须在离开(“out”)函数之前设置。
ref
数据必须在进入(“in”)函数之前设置。
数据可以在离开(“out”)函数之前设置。
out
是 ref
的更严格版本。
在方法体中,你需要在离开方法之前为所有的 out
参数赋值。
同时,对于赋给 out
参数的值是被忽略的,而 ref
要求它们被赋值。
因此,out
允许你做到:
int a, b, c = foo(out a, out b);
在这里,ref
需要a和b被分配。
ref
有“前置条件:变量一定被分配,后置条件:变量一定被分配”,而out
只有“后置条件:变量一定被分配”。(并且如预期的那样,具有较少前置条件的函数实现需要更多的要求) - Ben Voigt听起来是这样的:
out = 仅初始化/填充参数(参数必须为空),并返回一个 out 明文
ref = 引用,标准参数(可能带有值),但函数可以修改它。
out
传递的变量在分配之前无法读取,而ref
没有此限制。就是这样。 - Corey Ogburnref
是用于输入/输出参数,而out
则是仅输出参数。 - Tim S.out
变量必须被赋值。 - Corey Ogburn