C# 何时使用 in vs ref vs out

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1516876/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 18:19:33  来源:igfitidea点击:

When to use in vs ref vs out

c#

提问by Dale

Someone asked me the other day when they should use the parameter keyword outinstead of ref. While I (I think) understand the difference between the refand outkeywords (that has been asked before) and the best explanation seems to be that ref== inand out, what are some (hypothetical or code) examples where I should always use outand not ref.

前几天有人问我什么时候应该使用参数关键字out而不是ref. 虽然我(我认为)理解refout关键字(之前已经问过)之间的区别,并且最好的解释似乎是ref==inout,但我应该始终使用哪些(假设或代码)示例out而不是ref.

Since refis more general, why do you ever want to use out? Is it just syntactic sugar?

既然ref更一般,你为什么要使用out?它只是语法糖吗?

采纳答案by peterchen

You should use outunless you need ref.

out除非您需要,否则您应该使用ref

It makes a big difference when the data needs to be marshalled e.g. to another process, which can be costly. So you want to avoid marshalling the initial value when the method doesn't make use of it.

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

Beyond that, it also shows the reader of the declaration or the call whether the initial value is relevant (and potentially preserved), or thrown away.

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

As a minor difference, an out parameter needs not be initialized.

作为一个微小的区别,不需要初始化输出参数。

Example for out:

示例out

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

where GetBothNames is a method to retrieve two values atomically, the method won't change behavior whatever a and b are. If the call goes to a server in Hawaii, copying the initial values from here to Hawaii is a waste of bandwidth. A similar snippet using ref:

其中 GetBothNames 是一种以原子方式检索两个值的方法,无论 a 和 b 是什么,该方法都不会改变行为。如果呼叫转到夏威夷的服务器,将初始值从这里复制到夏威夷是浪费带宽。使用 ref 的类似片段:

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

could confuse readers, because it looks like the initial values of a and b are relevant (though the method name would indicate they are not).

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

Example for ref:

示例ref

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

Here the initial value is relevant to the method.

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

回答by Reed Copsey

Use out to denote that the parameter is not being used, only set. This helps the caller understand that you're always initializing the parameter.

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

Also, ref and out are not just for value types. They also let you reset the object that a reference type is referencing from within a method.

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

回答by leppie

outis more constraint version of ref.

outref.

In a method body, you need to assign to all outparameters before leaving the method. Also an values assigned to an outparameter is ignored, whereas refrequires them to be assigned.

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

So outallows you to do:

所以out允许你做:

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

where refwould require a and b to be assigned.

哪里ref需要分配 a 和 b。

回答by Adam Robinson

You're correct in that, semantically, refprovides both "in" and "out" functionality, whereas outonly provides "out" functionality. There are some things to consider:

你是对的,从语义上讲,ref提供“输入”和“输出”功能,而out只提供“输出”功能。有一些事情需要考虑:

  1. outrequires 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 outparameter that holds what the value will be if retrieved. It wouldn't make sense for the caller to pass a value intothis function, so outis 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 TryGetValuewhere the key isn't present).
  2. outand refparameters are marshaled differently when dealing with interop code
  1. out要求接受参数的方法必须在返回之前的某个时刻为变量赋值。您可以在某些键/值数据存储类(例如Dictionary<K,V>)中找到这种模式,其中具有TryGetValue. 此函数采用一个out参数,该参数保存检索后的值。这是没有意义的调用者传递一个值进入此功能,所以out被用来保证一定的价值会在通话结束后的变量,即使它是不是“真正”的数据(在的情况下TryGetValue,其中钥匙不存在)。
  2. outref参数在处理互操作代码时的封送方式不同

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 anothermemory 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 instanceare 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.

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

Passing with the refkeyword says that both the original variable andthe function parameter will actually point to the same memory location. This, again, affects only assignment semantics. If a new value is assigned to one of the variables, then because the other points to the same memory location the new value will be reflected on the other side.

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

回答by ParmesanCodice

Just to clarify on OP's comment that the use on ref and out is a "reference to a value type or struct declared outside the method", which has already been established in incorrect.

只是为了澄清 OP 的评论,在 ref 和 out 上的使用是“对在方法之外声明的值类型或结构的引用”,这已经被错误地建立了。

Consider the use of ref on a StringBuilder, which is a reference type:

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

As apposed to this:

与此相关:

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

回答by Lorenz Lo Sauer

It depends on the compile context (See Example below).

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

outand refboth denote variable passing by reference, yet refrequires the variable to be initialized before being passed, which can be an important difference in the context of Marshaling (Interop: UmanagedToManagedTransition or vice versa)

outref两者都表示通过引用传递变量,但ref需要在传递之前初始化变量,这可能是编组上下文中的一个重要区别(互操作:UmanagedToManagedTransition,反之亦然)

MSDN warns:

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.

不要将按引用传递的概念与引用类型的概念混淆。这两个概念并不相同。无论是值类型还是引用类型,都可以通过 ref 修改方法参数。通过引用传递值类型时没有装箱。

From the official MSDN Docs:

来自官方 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

out 关键字导致参数通过引用传递。这类似于 ref 关键字,不同之处在于 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.

ref 关键字导致参数通过引用传递,而不是通过值传递。通过引用传递的效果是,对方法中参数的任何更改都会反映在调用方法中的底层参数变量中。引用参数的值始终与基础参数变量的值相同。

We can verify that the out and ref are indeed the same when the argument gets assigned:

当参数被赋值时,我们可以验证 out 和 ref 确实相同:

CIL Example:

CIL 示例

Consider the following example

考虑下面的例子

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
}

in CIL, the instructions of myfuncOutand myfuncRefare identical as expected.

在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: no operation, ldloc: load local, stloc: stack local, ldarg: load argument, bs.s: branch to target....

nop:无操作,ldloc:加载本地,stloc:堆栈本地,ldarg:加载参数,bs.s:分支到目标......

(See: List of CIL instructions)

(参见:CIL 指令列表

回答by zneak

You need to use refif you plan to read and write to the parameter. You need to use outif you only plan to write. In effect, outis for when you'd need more than one return value, or when you don't want to use the normal return mechanism for output (but this should be rare).

ref如果您打算读取和写入参数,则需要使用。out如果您只打算编写,则需要使用。实际上,out当您需要多个返回值时,或者当您不想使用正常的返回机制进行输出时(但这应该很少见)。

There are language mechanics that assist these use cases. Refparameters must have been initialized before they are passed to a method (putting emphasis on the fact that they are read-write), and outparameters cannot be read before they are assigned a value, and are guaranteed to have been written to at the end of the method (putting emphasis on the fact that they are write only). Contravening to these principles results in a compile-time error.

有语言机制可以帮助这些用例。Ref参数在传递给方法之前必须已经初始化(强调它们是可读写的),并且out参数在赋值之前不能被读取,并且保证在结束时被写入方法(强调它们是只写的事实)。违反这些原则会导致编译时错误。

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

For instance, int.TryParsereturns a booland accepts an out intparameter:

例如,int.TryParse返回 abool并接受一个out int参数:

int value;
if (int.TryParse(numericString, out value))
{
    /* numericString was parsed into value, now do stuff */
}
else
{
    /* numericString couldn't be parsed */
}

This is a clear example of a situation where you need to output two values: the numeric result and whether the conversion was successful or not. The authors of the CLR decided to opt for outhere since they don't care about what the intcould have been before.

这是您需要输出两个值的情况的一个明显示例:数字结果和转换是否成功。CLR 的作者决定选择out这里,因为他们不关心int以前可能是什么。

For ref, you can look at Interlocked.Increment:

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

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

Interlocked.Incrementatomically increments the value of x. Since you need to read xto increment it, this is a situation where refis more appropriate. You totally care about what xwas before it was passed to Increment.

Interlocked.Increment原子地增加 的值x。由于您需要阅读x以增加它,因此这ref是更合适的情况。您完全关心x传递给Increment.

In the next version of C#, it will even be possible to declare variable in outparameters, adding even more emphasis on their output-only nature:

在 C# 的下一个版本中,甚至可以在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
}

回答by vml19

An argument passed as ref must be initialized before passing to the method whereas out parameter needs not to be initialized before passing to a method.

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

回答by Shivprasad Ktheitroadala

Below are some notes which i pulled from this codeproject article on C# Out Vs Ref

下面是我从这篇关于C# Out Vs Ref 的codeproject 文章中提取的一些笔记

  1. It should be used only when we are expecting multiple outputs from a function or a method. A thought on structures can be also a good option for the same.
  2. REF and OUT are keywords which dictate how data is passed from caller to callee and vice versa.
  3. In REF data passes two way. From caller to callee and vice-versa.
  4. In Out data passes only one way from callee to caller. In this case if Caller tried to send data to the callee it will be overlooked / rejected.
  1. 仅当我们期望函数或方法有多个输出时才应使用它。对结构的思考也是一个不错的选择。
  2. REF 和 OUT 是关键字,它们指示数据如何从调用者传递到被调用者,反之亦然。
  3. 在 REF 数据通过两种方式。从调用者到被调用者,反之亦然。
  4. In Out 数据仅通过一种方式从被调用者传递到调用者。在这种情况下,如果调用者试图向被调用者发送数据,它将被忽略/拒绝。

If you are a visual person then please see this yourtube video which demonstrates the difference practically https://www.youtube.com/watch?v=lYdcY5zulXA

如果你是一个视觉人士,那么请看这个 yourtube 视频,它实际上演示了不同之处https://www.youtube.com/watch?v=lYdcY5zulXA

Below image shows the differences more visually

下图更直观地显示了差异

C# Out Vs Ref

C# 输出与参考

回答by Ali Jamal

You can use the outcontextual keyword in two contexts (each is a link to detailed information), as a parameter modifier or in generic type parameter declarations in interfaces and delegates. This topic discusses the parameter modifier, but you can see this other topic for information on the generic type parameter declarations.

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

The outkeyword causes arguments to be passed by reference. This is like the refkeyword, except that refrequires that the variable be initialized before it is passed. To use an outparameter, both the method definition and the calling method must explicitly use the outkeyword. For example: C#

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
    }
}

Although variables passed as outarguments do not have to be initialized before being passed, the called method is required to assign a value before the method returns.

尽管作为out参数传递的变量在传递之前不必初始化,但被调用的方法需要在方法返回之前赋值。

Although the refand outkeywords 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 refargument and the other takes an outargument. The following code, for example, will not compile: C#

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

Overloading can be done, however, if one method takes a refor outargument and the other uses neither, like this: C#

但是,如果一个方法采用reforout参数而另一个不使用任何参数,则可以进行重载,如下所示:C#

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

Properties are not variables and therefore cannot be passed as outparameters.

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

For information about passing arrays, see Passing Arrays Using refand out(C# Programming Guide).

有关传递数组的信息,请参阅使用ref和传递数组out(C# 编程指南)。

You can't use the refand outkeywords for the following kinds of methods:

您不能将refandout关键字用于以下类型的方法:

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

Example

例子

Declaring an outmethod is useful when you want a method to return multiple values. The following example uses outto return three variables with a single method call. Note that the third argument is assigned to null. This enables methods to return values optionally. C#

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