何时使用ref vs out

有一天有人问我,他们应该使用参数关键字out而不是ref 。 虽然我(我认为)了解refout关键字之间的区别( 之前已经提到过 ),最好的解释似乎是ref == inout ,但是我应该总是这样(假设或代码)的例子是什么用掉out而不是ref

由于ref更一般,你为什么要out ? 这只是句法糖吗?

除非你需要ref你应该使用。

当需要将数据整理到另一个过程时,这会产生很大的差异,这可能是昂贵的。 所以你想避免在方法没有使用的时候编组初始值。

除此之外,它还向读者展示了声明或者调用的初始值是否相关(并且可能被保留),或者被抛弃。

作为一个小差异,out参数不需要被初始化。

out例子:

 string a, b; person.GetBothNames(out a, out b); 

其中GetBothNames是一个方法来primefaces检索两个值,该方法不会改变行为,无论一个和B是什么。 如果电话打到夏威夷的一台服务器,从这里复制初始值到夏威夷是浪费带宽。 一个类似的片段使用ref:

 string a = String.Empty, b = String.Empty; person.GetBothNames(ref a, ref b); 

可能会使读者感到困惑,因为它看起来像a和b的初始值是相关的(尽pipe方法名称表示它们不是)。

示例为ref

 string name = textbox.Text; bool didModify = validator.SuggestValidName(ref name); 

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

用out表示参数没有被使用,只设置。 这有助于调用者理解您始终在初始化参数。

另外,ref和out不只是值types。 它们还允许您重置引用types在方法内引用的对象。

在这方面你是正确的,在语义上, ref提供“in”和“out”function,而out只提供“out”function。 有一些事情要考虑:

  1. out要求接受参数的方法必须在返回之前的某个时刻赋值给variables。 您可以在一些键/值数据存储类(如Dictionary<K,V> )中find此模式,其中有TryGetValue函数。 这个函数接受一个out参数,它保存了检索值的值。 调用者将一个值传递给这个函数是没有意义的,所以out被用来保证调用后的variables中有一些值,即使它不是“真实的”数据(在TryGetValue的关键不存在)。
  2. 在处理互操作代码时, outref参数是不同的

另外,需要注意的是,虽然引用types和值types的值的性质不同,但应用程序中的每个variables都指向一个保存值的内存位置 ,即使对于引用types也是如此。 恰恰相反,对于引用types,内存位置中包含的值是另一个内存位置。 当您将值传递给一个函数(或执行任何其他variables赋值)时,该variables的值将被复制到另一个variables中。 对于值types,这意味着types的整个内容被复制。 对于参考types,这意味着内存位置被复制。 无论哪种方式,它都会创buildvariables中包含的数据的副本。 这个唯一真正的相关性涉及分配语义; 当分配variables或按值传递(默认值)时,当对原始variables(或新variables)进行新分配时,不会影响其他variables。 在引用types的情况下,是的,对实例所做的更改在双方都可用,但这是因为实际variables只是指向另一个内存位置的指针; variables的内容 – 内存位置 – 实际上并没有改变。

用关键字ref传递,原来的variables函数参数都会指向同一个内存位置。 这又一次只影响分配语义。 如果一个新值被分配给其中一个variables,那么因为另一个指向相同的内存位置,新值将反映在另一侧。

这取决于编译上下文(请参阅下面的示例)。

outref都表示variables传递的引用,但是ref需要variables在被传递之前被初始化,这可能是一个重要的区别,在编组(context):UmanagedToManagedTransition或者反之亦然)

MSDN警告 :

Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.

从官方的MSDN文档:

  • out

The out keyword causes arguments to be passed by reference. This is similar to the ref keyword, except that ref requires that the variable be initialized before being passed

  • ref

The ref keyword causes an argument to be passed by reference, not by value. The effect of passing by reference is that any change to the parameter in the method is reflected in the underlying argument variable in the calling method. The value of a reference parameter is always the same as the value of the underlying argument variable.

我们可以validationout和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中, myfuncOutmyfuncRef的指示与预期相同。

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

如果您打算读取和写入参数,则需要使用ref 。 如果你只打算写,你需要用掉。 实际上,当你需要多于一个返回值,或者当你不想使用正常的返回机制来输出(但这应该是罕见的)的时候, 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的作者决定selectout ,因为他们不关心以前的内容。

对于ref ,你可以看看Interlocked.Increment

 int x = 4; Interlocked.Increment(ref x); 

Interlocked.Increment以primefaces方式递增x的值。 既然你需要读x来增加它,这是一个ref更合适的情况。 您完全关心x在传递到Increment之前的内容。

在下一个版本的C#中,甚至可以在out参数中声明variables,甚至更强调它们的输出特性:

 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 } 

下面是我从C#Out Vs Ref的这个codeproject文章中得到的一些注释

  1. 只有当我们期待从一个函数或一个方法得到多个输出时,才应该使用它。 对结构的思考也是一个很好的select。
  2. REF和OUT是决定数据如何从调用者传递到被调用者的关键字,反之亦然。
  3. 在REF数据通过两种方式。 从呼叫者到被呼叫者,反之亦然。
  4. 在“输出”中,数据只能从被调用方传递到调用方。 在这种情况下,如果调用者试图发送数据给被调用者,它将被忽略/拒绝。

如果你是一个视觉的人,那么请看这个yourtubevideo,实际上显示差异https://www.youtube.com/watch?v=lYdcY5zulXA

下图显示了更多的视觉差异

C#Out Vs Ref

out是更多的ref约束版本。

在方法体中,您需要在离开方法之前分配所有的参数。 此外,分配给out参数的值将被忽略,而ref需要分配它们。

所以out允许你做:

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

ref需要a和b分配。

听起来如何:

out =只初始化/填充一个参数(参数必须是空的)将其返回平淡

ref = reference,标准参数(也许有值),但是函数可以修改它。

只是为了澄清OP的评论,在ref和out上的使用是“对在方法之外声明的值types或结构的引用”,这已经被build立了不正确的。

考虑在一个StringBuilder上使用ref,它是一个引用types:

 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 

作为ref传递的参数必须在传递给方法之前初始化,而out参数在传递给方法之前不需要初始化。

您可以在两个上下文(每个都是指向详细信息的链接)中使用out关联关键字,作为参数修饰符或接口和委托中的genericstypes参数声明。 本主题讨论参数修饰符,但您可以看到这个其他主题以获取有关genericstypes参数声明的信息。

out关键字导致参数通过引用传递。 这就像ref关键字,除了ref要求variables在传递之前被初始化。 要使用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 } } 

虽然作为outparameter passing的variables在传递之前不必被初始化,但被调用方法需要在方法返回之前分配一个值。

虽然refout关键字会导致不同的运行时行为,但在编译时它们不被认为是方法签名的一部分。 因此,如果唯一的区别是一个方法需要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) { } } 

重载可以完成,但是,如果一个方法需要一个refout参数,另一个不使用,如下所示:C#

 class OutOverloadExample { public void SampleMethod(int i) { } public void SampleMethod(out int i) { i = 5; } } 

属性不是variables,因此不能作为outparameter passing。

有关传递数组的信息,请参见使用refout传递数组(C#编程指南)。

您不能在以下几种方法中使用refout关键字:

 Async methods, which you define by using the async modifier. Iterator methods, which include a yield return or yield break statement. 

当你想要一个方法返回多个值时,声明一个out方法是很有用的。 以下示例使用out方法通过一个方法调用返回三个variables。 请注意,第三个参数分配给null。 这使方法可以select返回值。 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; } } 

你为什么要用掉?

让别人知道variables在被调用方法返回时会被初始化!

如上所述:“对于out参数, 调用方法需要在方法返回之前分配一个值

例:

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

基本上都是refout用于在方法之间传递对象/值

out关键字导致参数通过引用传递。 这就像ref关键字,除了ref要求variables在传递之前被初始化。

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

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

什么是“ref”参考types的用法?

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

你知道吗?

  1. 虽然ref和out关键字会导致不同的运行时行为,但在编译时它们不被认为是方法签名的一部分。 因此,如果唯一的区别是一个方法需要ref参数,另一个方法需要out参数,则方法不能被重载。

  2. 您不能在以下几种方法中使用ref和out关键字:

    • asynchronous方法,通过使用async修饰符定义。
    • 迭代器方法,其中包括一个收益率回报或收益率断裂声明。
  3. 属性不是variables,因此不能作为输出parameter passing。

关于C#7的额外说明:
在C#7中,不需要使用out来预先声明variables。 所以这样的代码:

 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中的新增function