前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
当前回答
下面是我从这篇关于c# Out Vs Ref的codeproject文章中摘录的一些注释
只有当我们期望从一个函数或方法得到多个输出时,才应该使用它。对结构的思考也是一个不错的选择。 REF和OUT是关键字,它们指示数据如何从调用方传递到被调用方,反之亦然。 在REF中,数据以两种方式传递。从呼叫者到被呼叫者,反之亦然。 In Out数据仅以一种方式从被调用方传递给调用方。在这种情况下,如果Caller试图向被调用方发送数据,它将被忽略/拒绝。
如果你是一个视觉人,那么请看看这个yourtube视频,它演示了实际的区别https://www.youtube.com/watch?v=lYdcY5zulXA
下图更直观地显示了差异
其他回答
Out是ref的约束版本。
在方法体中,需要在离开方法之前为所有out参数赋值。 另外,分配给out形参的值将被忽略,而ref要求分配这些值。
out允许你这样做:
int a, b, c = foo(out a, out b);
其中ref需要分配a和b。
需要注意的是,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.
它取决于编译上下文(参见下面的示例)。
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指令列表)
你应该使用out,除非你需要参考。
当数据需要被编组到另一个进程时,它会产生很大的不同,这可能会花费很高。因此,您希望避免在方法不使用初始值时编组初始值。
除此之外,它还向声明或调用的读者显示初始值是相关的(并可能被保留),还是被丢弃。
作为一个微小的区别,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的初始值是相关的(尽管方法名表明它们不是)。
引用的例子:
string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);
这里的初始值与方法相关。
可以在两个上下文中使用out上下文关键字(每个上下文都是到详细信息的链接),作为参数修饰符或在接口和委托中的泛型类型参数声明中使用。本主题讨论参数修饰符,但有关泛型类型参数声明的信息,请参阅另一个主题。
out关键字使参数通过引用传递。这类似于ref关键字,只不过ref要求在传递变量之前对其进行初始化。要使用out形参,方法定义和调用方法都必须显式使用out关键字。例如: c#
class OutExample
{
static void Method(out int i)
{
i = 44;
}
static void Main()
{
int value;
Method(out value);
// value is now 44
}
}
虽然作为输出参数传递的变量在传递之前不必初始化,但被调用的方法需要在方法返回之前赋值。
尽管ref和out关键字会导致不同的运行时行为,但它们在编译时不被视为方法签名的一部分。因此,如果唯一的区别是一个方法使用ref参数,而另一个方法使用out参数,则方法不能重载。例如,下面的代码将无法编译: c#
class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on ref and out".
public void SampleMethod(out int i) { }
public void SampleMethod(ref int i) { }
}
然而,如果一个方法使用ref或out参数,而另一个方法两者都不使用,则可以进行重载,如下所示: c#
class OutOverloadExample
{
public void SampleMethod(int i) { }
public void SampleMethod(out int i) { i = 5; }
}
属性不是变量,因此不能作为参数传递。
有关传递数组的信息,请参见使用ref和out传递数组(c#编程指南)。
以下类型的方法不能使用ref和out关键字:
Async methods, which you define by using the async modifier.
Iterator methods, which include a yield return or yield break statement.
例子
当您希望一个方法返回多个值时,声明一个out方法非常有用。下面的示例使用out通过一个方法调用返回三个变量。注意,第三个参数被赋值为null。这使得方法可以有选择地返回值。 c#
class OutReturnExample
{
static void Method(out int i, out string s1, out string s2)
{
i = 44;
s1 = "I've been returned";
s2 = null;
}
static void Main()
{
int value;
string str1, str2;
Method(out value, out str1, out str2);
// value is now 44
// str1 is now "I've been returned"
// str2 is (still) null;
}
}