views:

283

answers:

2

When I make an assignment to an out or ref parameter, is the value immediately assigned to the reference provided by the caller, or are the out and ref parameter values assigned to the references when the method returns? If the method throws an exception, are the values returned?

For example:

int callerOutValue = 1;
int callerRefValue = 1;
MyMethod(123456, out callerOutValue, ref callerRefValue);

bool MyMethod(int inValue, out int outValue, ref int refValue)
{
    outValue = 2;
    refValue = 2;

    throw new ArgumentException();

    // Is callerOutValue 1 or 2?
    // Is callerRefValue 1 or 2?
}
+20  A: 

Since ref and out parameters allow a method to work with the actual references that the caller passed in, all changes to those references are reflected immediately to the caller when control is returned.

This means in your example above (if you were to catch the ArgumentException of course), outValue and refValue would both be set to 2.

It is also important to note that out and ref are identical concepts at an IL level - it is only the C# compiler that enforces the extra rule for out that requires that a method set its value prior to returning. So from an CLR perspective outValue and refValue have identical semantics and are treated the same way.

Andrew Hare
Hmm... that's not what I would have expected, but test confirms it! +1.
Michael Bray
This is useful information. :O
Sapph
I remember micro-optimizing a method that received an `out` parameter. The method was called in a very tight loop and I wanted to remove the required initialization every time it was called as the parameter didn't change, so I made it take a `ref` parameter and it performed measurably worse. Does anyone have an explanation for this, or was it a fluke?
JulianR
Julian: When you're using an out parameter, the compiler (and IL) don't initialize the variable in advance - just use it. With ref, the parameter has to be initialized before the call. This adds instructions.
Reed Copsey
I agree. But the situation was that I ended up calling the method that took an `out` parameter millions of times a second, and in that method, I initialized that variable every time (was forced to, as it was an `out` param) so I figured I could optimize that by using `ref` and initializing the variable once instead of millions of times, as it didn't change (only used it to avoid copying a large struct), but that was slower.
JulianR
+6  A: 

Andrew is correct; I will merely add a couple of extra details.

First off, the correct way to think of out/ref parameters is that they are aliases for variables. That is, when you have a method M(ref int q) and call it M(ref x), q and x are two different names for exactly the same variable. A variable is a storage location; you store something in q, you're storing it in x too, because they are two different names for the same location.

Second, the alternative you're describing is called "copy in / copy out" referencing. In this scheme there are two storage locations and the contents of one are copied in upon the function call beginning, and copied back out when its done. As you note, the semantics of copy-in-copy-out are different than the semantics of alias references when exceptions are thrown.

They are also different in bizarre situations like this:

void M(ref int q, ref int r)
{  
  q = 10;
  r = 20;
  print (q);
}

...

M(ref x, ref x);

In aliasing, x, q and r are all the same storage location, so this prints 20. In copy-in-copy-out referencing, this would print 10, and the final value of x would depend on whether the copy-out went left to right or right to left.

Finally, if I recall correctly, there are rare and bizarre scenarios in the implementation of expression trees where we actually implement copy-in-copy-out semantics on ref parameters. I should review that code and see if I can remember what exactly those scenarios are.

Eric Lippert
Thanks...very interesting!
Zach Johnson