前几天有人问我他们什么时候应该使用参数关键字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

下图更直观地显示了差异

其他回答

关于c# 7的额外注意事项: 在c# 7中,不需要使用out预先声明变量。代码是这样的:

public void PrintCoordinates(Point p)
{
  int x, y; // have to "predeclare"
  p.GetCoordinates(out x, out y);
  WriteLine($"({x}, {y})");
}

可以这样写:

public void PrintCoordinates(Point p)
{
  p.GetCoordinates(out int x, out int y);
  WriteLine($"({x}, {y})");
}

来源:c# 7的新特性。

可以在两个上下文中使用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;
    }
}

你为什么要用out?

让其他人知道当变量从被调用的方法返回时将被初始化!

如上所述: 对于out形参,调用方法需要在方法返回之前赋值。

例子:

Car car;
SetUpCar(out car);
car.drive();  // You know car is initialized.

如果计划读取和写入参数,则需要使用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
}

它取决于编译上下文(参见下面的示例)。

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指令列表)