前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。
既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?
当前回答
你为什么要用out?
让其他人知道当变量从被调用的方法返回时将被初始化!
如上所述: 对于out形参,调用方法需要在方法返回之前赋值。
例子:
Car car;
SetUpCar(out car);
car.drive(); // You know car is initialized.
其他回答
它取决于编译上下文(参见下面的示例)。
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表示该参数未被使用,仅被设置。这有助于调用者理解您总是在初始化参数。
此外,ref和out不仅适用于值类型。它们还允许您重置引用类型从方法中引用的对象。
作为ref传递的参数必须在传递给方法之前初始化,而out形参在传递给方法之前不需要初始化。
下面是我从这篇关于c# Out Vs Ref的codeproject文章中摘录的一些注释
只有当我们期望从一个函数或方法得到多个输出时,才应该使用它。对结构的思考也是一个不错的选择。 REF和OUT是关键字,它们指示数据如何从调用方传递到被调用方,反之亦然。 在REF中,数据以两种方式传递。从呼叫者到被呼叫者,反之亦然。 In Out数据仅以一种方式从被调用方传递给调用方。在这种情况下,如果Caller试图向被调用方发送数据,它将被忽略/拒绝。
如果你是一个视觉人,那么请看看这个yourtube视频,它演示了实际的区别https://www.youtube.com/watch?v=lYdcY5zulXA
下图更直观地显示了差异
可以在两个上下文中使用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;
}
}