When to use in vs ref vs out

2018-12-31 18:41发布

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

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

标签: c#
15条回答
残风、尘缘若梦
2楼-- · 2018-12-31 18:45

You need to use ref if you plan to read and write to the parameter. You need to use out if you only plan to write. In effect, out is 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).

There are language mechanics that assist these use cases. Ref parameters must have been initialized before they are passed to a method (putting emphasis on the fact that they are read-write), and out parameters 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.

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

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

For instance, int.TryParse returns a bool and accepts an out int parameter:

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 out here since they don't care about what the int could have been before.

For ref, you can look at Interlocked.Increment:

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

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

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

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
}
查看更多
春风洒进眼中
3楼-- · 2018-12-31 18:47

out is more constraint version of ref.

In a method body, you need to assign to all out parameters before leaving the method. Also an values assigned to an out parameter is ignored, whereas ref requires them to be assigned.

So out allows you to do:

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

where ref would require a and b to be assigned.

查看更多
不流泪的眼
4楼-- · 2018-12-31 18:50

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

  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.

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

Below image shows the differences more visually

C# Out Vs Ref

查看更多
萌妹纸的霸气范
5楼-- · 2018-12-31 18:51

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

out and ref both denote variable passing by reference, yet ref requires the variable to be initialized before being passed, which can be an important difference in the context of Marshaling (Interop: UmanagedToManagedTransition or vice versa)

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.

From the official MSDN Docs:

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.

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

CIL Example:

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 myfuncOut and myfuncRef are identical as expected.

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

(See: List of CIL instructions )

查看更多
刘海飞了
6楼-- · 2018-12-31 18:55

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.

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

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
查看更多
时光乱了年华
7楼-- · 2018-12-31 18:56

How it sounds:

out = only initialize/fill a parameter (the parameter must be empty) return it out plain

ref = reference, standard parameter (maybe with value), but the function can modifiy it.

查看更多
登录 后发表回答