前几天有人问我他们什么时候应该使用参数关键字out而不是ref。虽然我(我认为)理解ref和out关键字之间的区别(之前已经问过了),最好的解释似乎是ref == in和out,有什么(假设或代码)的例子,我应该总是使用out而不是ref。

既然ref更通用,为什么还要用out呢?它只是语法上的糖吗?


使用out表示该参数未被使用,仅被设置。这有助于调用者理解您总是在初始化参数。

此外,ref和out不仅适用于值类型。它们还允许您重置引用类型从方法中引用的对象。


Out是ref的约束版本。

在方法体中,需要在离开方法之前为所有out参数赋值。 另外,分配给out形参的值将被忽略,而ref要求分配这些值。

out允许你这样做:

int a, b, c = foo(out a, out b);

其中ref需要分配a和b。


你应该使用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);

这里的初始值与方法相关。


你是正确的,在语义上,ref同时提供“in”和“out”功能,而out只提供“out”功能。有一些事情需要考虑:

out requires that the method accepting the parameter MUST, at some point before returning, assign a value to the variable. You find this pattern in some of the key/value data storage classes like Dictionary<K,V>, where you have functions like TryGetValue. This function takes an out parameter that holds what the value will be if retrieved. It wouldn't make sense for the caller to pass a value into this function, so out is used to guarantee that some value will be in the variable after the call, even if it isn't "real" data (in the case of TryGetValue where the key isn't present). out and ref parameters are marshaled differently when dealing with interop code

Also, as an aside, it's important to note that while reference types and value types differ in the nature of their value, every variable in your application points to a location of memory that holds a value, even for reference types. It just happens that, with reference types, the value contained in that location of memory is another memory location. When you pass values to a function (or do any other variable assignment), the value of that variable is copied into the other variable. For value types, that means that the entire content of the type is copied. For reference types, that means that the memory location is copied. Either way, it does create a copy of the data contained in the variable. The only real relevance that this holds deals with assignment semantics; when assigning a variable or passing by value (the default), when a new assignment is made to the original (or new) variable, it does not affect the other variable. In the case of reference types, yes, changes made to the instance are available on both sides, but that's because the actual variable is just a pointer to another memory location; the content of the variable--the memory location--didn't actually change.

与ref关键字一起传递表示原始变量和函数参数实际上都指向相同的内存位置。同样,这只影响赋值语义。如果将一个新值赋给其中一个变量,那么由于其他变量指向相同的内存位置,新值将反映在另一侧。


只是在OP的注释中澄清一下,对ref和out的使用是“对在方法外部声明的值类型或结构的引用”,这已经在incorrect中建立了。

考虑在StringBuilder上使用ref,它是一种引用类型:

private void Nullify(StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// Hi Guy

与此相反:

private void Nullify(ref StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(ref sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// NullReferenceException

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

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


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

作为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;
    }
}

读音: Out =只初始化/填充一个参数(参数必须为空),将其plain返回 Ref = reference,标准参数(可能带有value),但函数可以修改它。


你为什么要用out?

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

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

例子:

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

基本上ref和out都用于在方法之间传递对象/值

out关键字使参数通过引用传递。这类似于ref关键字,只不过ref要求在传递变量之前对其进行初始化。

out:参数没有初始化,必须在方法中初始化

ref:参数已经初始化,可以在方法中读取和更新。

引用类型的“ref”有什么用?

您可以将给定的引用更改为不同的实例。

你知道吗?

Although the ref and out keywords cause different run-time behavior, they are not considered part of the method signature at compile time. Therefore, methods cannot be overloaded if the only difference is that one method takes a ref argument and the other takes an out argument. You can't use the ref and out keywords for the following kinds of methods: Async methods, which you define by using the async modifier. Iterator methods, which include a yield return or yield break statement. Properties are not variables and therefore cannot be passed as out parameters.


关于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的新特性。


在c#中如何使用in或out或ref ?

All keywords in C# have the same functionality but with some boundaries. in arguments cannot be modified by the called method. ref arguments may be modified. ref must be initialized before being used by caller it can be read and updated in the method. out arguments must be modified by the caller. out arguments must be initialized in the method Variables passed as in arguments must be initialized before being passed in a method call. However, the called method may not assign a value or modify the argument.

以下类型的方法不能使用in、ref和out关键字:

Async方法,通过使用Async修饰符来定义。 迭代器方法,其中包括yield return或yield break语句。


需要注意的是,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.


还是觉得需要好好总结一下,这就是我想出来的。

总结,

当我们在函数内部时,这就是我们如何指定变量数据访问控制,

in = R

out = W必须在R之前

参考 = R+W


解释,

in

函数只能读取该变量。

out

变量不能先初始化,因为, 函数必须先写后读。

ref

函数可以读/写该变量。


为什么要这样命名呢?

关注数据被修改的地方,

in

数据必须在输入(in)函数之前设置。

out

数据必须只在离开(out)函数之前设置。

ref

在输入函数之前必须设置数据。 在离开(out)函数之前可以设置数据。