何时使用vs ref vs out

时间:2009-10-04 17:02:11

标签: c#

有一天有人问我应该使用参数关键字out而不是ref。虽然我(我认为)了解refout关键字(已经asked before)之间的差异,但最好的解释似乎是ref == {{ 1}}和in,我应该始终使用out而不是out的一些(假设或代码)示例。

由于ref更为通用,您为什么要使用ref?它只是语法糖吗?

17 个答案:

答案 0 :(得分:381)

除非您需要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);

此处初始值与方法相关。

答案 1 :(得分:71)

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

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

答案 2 :(得分:36)

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

  1. out要求接受参数的方法必须在返回之前的某个时刻为变量赋值。您可以在某些键/值数据存储类中找到此模式,例如Dictionary<K,V>,其中包含TryGetValue等函数。此函数采用out参数来保存检索时的值。调用者将值传递给这个函数是没有意义的,所以out用于保证调用后某个值将在变量中,即使它不是“真实的”数据(在TryGetValue的情况下,密钥不存在)。
  2. 处理互操作代码时,
  3. outref参数的编组方式不同
  4. 另外,重要的是要注意虽然引用类型和值类型的值的性质不同,但应用程序中的每个变量都指向一个包含值的内存位置 ,甚至是参考类型。只是在引用类型中,内存位置中包含的值是另一个内存位置。将值传递给函数(或执行任何其他变量赋值)时,该变量的值将复制到另一个变量中。对于值类型,这意味着将复制该类型的整个内容。对于引用类型,这意味着将复制内存位置。无论哪种方式,它确实创建了变量中包含的数据的副本。唯一真正的相关性涉及赋值语义;当分配变量或传递值(默认值)时,当对原始(或新)变量进行新赋值时,它不会影响另一个变量。对于引用类型,是的,对实例所做的更改在两端都可用,但这是因为实际变量只是指向另一个内存位置的指针;变量的内容 - 内存位置 - 实际上没有改变。

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

答案 3 :(得分:25)

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

outref都表示通过引用传递的变量,但ref要求在传递之前初始化变量,这可能是Marshaling(Interop)上下文中的一个重要区别:UmanagedToManagedTransition,反之亦然)

MSDN warns

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 :加载本地, stloc :堆栈本地, ldarg :加载参数, bs.s :分支到目标....

(参见:List of CIL instructions

答案 4 :(得分:20)

以下是我在C# Out Vs Ref

上的代码项目文章中提取的一些注意事项
  1. 只有在我们期望函数或方法的多个输出时才应该使用它。对结构的思考也可以是一个很好的选择。
  2. REF和OUT是决定数据如何从调用者传递给被调用者的关键字,反之亦然。
  3. REF数据传递两种方式。从呼叫者到被呼叫者,反之亦然。
  4. 在Out数据中,只有一种方式从被叫方传递给调用方。在这种情况下,如果Caller尝试向被叫方发送数据,则会被忽略/拒绝。
  5. 如果您是视觉人士,请参阅此YouTube视频,该视频实际显示了差异https://www.youtube.com/watch?v=lYdcY5zulXA

    下图更直观地显示差异

    C# Out Vs Ref

答案 5 :(得分:17)

如果您打算读取和写入参数,则需要使用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以前的情况。

对于ref,您可以查看Interlocked.Increment

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

Interlocked.Increment以原子方式递增x的值。由于您需要阅读x来增加它,因此ref更合适。在将x传递给Increment之前,您完全关心out

在下一版本的C#中,甚至可以在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 } 参数中声明变量,更加强调其仅输出性质:

{{1}}

答案 6 :(得分:7)

outref的更多约束版本。

在方法体中,您需要在离开方法之前分配所有out参数。 此外,还会忽略分配给out参数的值,而ref则需要分配这些值。

所以out允许你这样做:

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

其中ref需要分配a和b。

答案 7 :(得分:7)

听起来如何:

out =只初始化/填充参数(参数必须为空)返回 out plain

ref =引用,标准参数(可能带有值),但函数可以修改它。

答案 8 :(得分:6)

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

虽然作为out参数传递的变量在传递之前不必初始化,但是需要被调用的方法在方法返回之前分配一个值。

虽然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; }
}

属性不是变量,因此不能作为out参数传递。

有关传递数组的信息,请参阅使用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通过单个方法调用返回三个变量。请注意,第三个参数指定为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;
    }
}

答案 9 :(得分:5)

只是为了澄清OP的注释,在ref和out上的使用是“对在方法之外声明的值类型或结构的引用”,这已经在不正确的情况下建立。

考虑在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

答案 10 :(得分:5)

如何在C#中使用inoutref

  • C#中的所有关键字具有相同的功能,但具有一些边界
  • in参数不能被调用的方法修改。
  • ref参数可以修改。
  • ref必须先被调用方初始化,然后才能在方法中读取和更新。
  • out参数必须由调用方修改。
  • out参数必须在方法中初始化
  • 作为in参数传递的变量必须在方法调用中传递之前进行初始化。但是,被调用的方法不能分配值或修改参数。

您不能将inrefout关键字用于以下几种方法:

    您可以使用async修饰符定义的
  • 异步方法
  • 迭代器方法,其中包括yield returnyield break语句。

答案 11 :(得分:4)

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

答案 12 :(得分:4)

  

你为什么要用完?

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

如上所述: &#34;对于out参数,需要调用方法在方法返回之前指定值。&#34;

示例:

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

答案 13 :(得分:4)

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

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

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

中初始化

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

“ref”对参考类型有什么用?

您可以将给定引用更改为其他实例。

您知道吗?

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

  2. 您不能将ref和out关键字用于以下几种方法:

    • 使用async修饰符定义的异步方法。
    • 迭代器方法,包括yield return或yield break语句。
  3. 属性不是变量,因此不能作为out参数传递。

答案 14 :(得分:4)

关于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})");
}

来源:What's new in C# 7.

答案 15 :(得分:2)

仍然感到需要一个很好的摘要,这是我想出的。

摘要

当我们进入函数时,这就是我们指定变量数据访问控件的方式,

in = R

out = R必须在W之前

ref = R + W


说明

in

函数只能读取该变量。

out

不得首先初始化变量,因为
必须阅读之前,必须先必须写该功能。

ref

函数可以读/写该变量。


为什么这样命名?

关注数据修改的地方,

in

必须仅在输入(输入)功能之前设置数据。

out

必须仅在退出(退出)功能之前设置数据。

ref

在进入(输入)功能之前必须先设置数据。
可以在退出(退出)功能之前设置数据。

答案 16 :(得分:0)

应注意,inC# ver 7.2起是有效关键字:

  

in参数修饰符在C#7.2和更高版本中可用。早期版本会生成编译器错误CS8107(“ C#7.0中不提供功能'只读引用'。请使用语言版本7.2或更高版本。”)要配置编译器语言版本,请参阅选择C#语言版本。

     

...

     

in关键字使参数通过引用传递。它使形式参数成为参数的别名,该参数必须是变量。换句话说,对参数进行的任何操作都在参数上进行。就像ref或out关键字,不同之处在于in参数不能被调用的方法修改。可以修改ref参数,而out参数必须由被调用的方法修改,并且这些修改在调用上下文中是可见的。