Question

Basic manuals on C# state that to change a value type when passed to another method you must use the out or ref keywords, etc.

For example:

int Loop(int counter)
{
   return(++counter);
}

void ClickIt ()
{
    int count = 0;
    for (int c1 = 0; c1 < 10; c1++) 
    {
        count = Loop(count);
        Console.Writeline(count);
    }
}

Here, ClickIt outputs the following result: 1, 2, 3, 4, ... 10

In the example, count (a value type), which is being passed from the method ClickIt to the method Loop without out or ref, is being changed in Loop. And Loop then returns count to the calling method ClickIt which picks up the change to count.

So, my question is: When is a value type, when passed as an argument to another method, required to use out or ref so that the value can be changed?

Was it helpful?

Solution

You have an incorrect understanding of the meaning of "ref", and you are also mixing up values and variables. These are common mistakes.

Let's go back to basics.

A variable is a storage location which can contain a value.

Let's simplify your program:

int M(int x)
{
    x++;
    return x;
}
void N()
{
    int y = 0;
    y = M(y);
}

If N is called, what happens?

Imagine a variable is a drawer that can contain a sheet of paper. We make a drawer and label it y. In y we put a piece of paper that says "0". Now we call M(y). What happens?

We make a new drawer labeled "x" and we make a photocopy of the piece of paper in "y". We put the copy in drawer x. y contains a piece of paper that says 0, and x contains a different piece of paper that also contains 0.

Now in M we increment x. What happens? We make a new piece of paper that says 1, throw away the old one, and put the new one in drawer x.

Now we make a photocopy of the value in x, so we have another piece of paper that says 1. When M returns we put that piece of paper into y and throw away the 0 that is already in there.

Did M modify y? No. M only modified x. N modified y, twice. Once when y was created, and once after M returned.

Notice that we made two copies. First, we made a copy of y on the way in to M and copied it to x, and then we made a copy of x on the way out and copied it to y.

Now suppose we have

void P(ref int b)
{
   b++;
}
void Q()
{
    int c = 0;
    P(ref c);
}

We run Q. What happens? We make a drawer called c and put a piece of paper in it that says "0". What happens when we call P? Something different. This time we make a drawer called b and put a piece of paper in it that says "don't use this drawer! Every time you try to use this drawer, use c instead!" That is, b refers its behaviour to c.

Now P tries to increment b. It tries to get a value out of b, but discovers that b says no, use c. So it looks in c, finds 0, makes a new piece of paper that says 1, replaces the contents of b -- no, wait, we need to replace the contents of c -- with 1, and returns. So c is updated to 1.

Does it make sense why these two things are different? The first is called copy in, copy out because we make a photocopy of y on the way in to M, and a copy of x on the way out. The second is called by reference because b refers its behaviour to c; no values are copied.

OTHER TIPS

In your sample you actually do not modify passed count variable. When it's passed, a copy inside a Loop function scope is being created. Then modification done, you return in back and set to your count variable.

Actually, the purpose of:

  1. ref - is that variable should be already initialized before passing into function. And copy is not created inside. You modify passed variable directly. As result - you don't need to return modified value and set it back to your variable.
  2. out - it does not require passed variable to be initialized before passing into your function. But it actually MUST be initialized inside that function.

Hope that's all.

Long comment... Your code could be confusing - make sure to separate result of function from parameter:

void ClickIt ()
{
    int count =0;
    for (int c1 =0; c1 < 10; c1++) 
    {
        var resultCount = Loop(count);
        Console.Writeline("Result:{0}, count:{1}", resultCount, count);
    }
}

Answer (opinion based) - you should almost never use out/ref - it is much harder to reason about than return values. Such functions also are hard to use in LINQ/lambda expression due to need of arguments to be variables.

Common case when it somewhat acceptable is when function returns more than one result (like TryParse), but consider if some other return type (i.e. nullable int?) would work too.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top