何时使用vs ref vs out

*爱你&永不变心* 提交于 2020-02-26 21:42:11

前几天有人问我应该使用参数关键字out而不是ref 。 虽然我(我认为)理解了refout关键字之间的差异( 之前已经提到过 ),最好的解释似乎是ref == in and out ,什么是一些(假设的或代码的)例子我应该总是out而不是ref

由于ref更为通用,为什么你想out ? 它只是语法糖吗?


#1楼

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

outref都表示通过引用传递的变量,但ref要求变量在传递之前被初始化,这可能是Marshaling上下文中的一个重要区别(Interop: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文档:

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

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.

在赋值参数时,我们可以验证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中, 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 :load local, stloc :stack local, ldarg :load参数, bs.s :branch to target ....

(参见: CIL指令列表


#2楼

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

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


#3楼

outref更多约束版本。

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

所以out允许你这样做:

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

其中ref需要分配a和b。


#4楼

你应该用out除非你需要ref

当数据需要被编组到例如另一个过程时,这会产生很大的不同,这可能是昂贵的。 因此,当方法不使用它时,您希望避免编组初始值。

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

作为次要差异,不需要初始化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的初始值是相关的(虽然方法名称表明它们不是)。

ref示例:

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

这里初始值与方法相关。


#5楼

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

  1. out要求接受参数的方法必须在返回之前的某个时刻为变量赋值。 您可以在某些键/值数据存储类中找到此模式,例如Dictionary<K,V> ,其中包含TryGetValue等函数。 此函数采用out参数来保存检索时的值。 这是没有意义的来电者传递一个值这个功能,所以out是用来保证一定的价值会在通话结束后的变量,即使它是不是“真正”的数据(在的情况下,不存在密钥的TryGetValue )。
  2. 在处理互操作代码时, outref参数的编组方式不同

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

使用ref关键字传递说原始变量函数参数实际上都指向相同的内存位置。 这再次只影响赋值语义。 如果为其中一个变量分配了新值,那么因为另一个指向同一个内存位置,新值将反映在另一侧。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!