19
votes

If I did not get this terribly wrong, this behaviour is strange for me. Rather than explaining, I'll post a sample code below and please tell me why does I get output x and not y.

    private void button1_Click(object sender, EventArgs e)
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(l);
        MessageBox.Show(l.Count.ToString());
    }

    private void Fuss(List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

Output should, I assume would be 3. But I get the output as 5. I understand the output can be 5 if I do this:

    private void button1_Click(object sender, EventArgs e)
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(ref l);
        MessageBox.Show(l.Count.ToString());
    }

    private void Fuss(ref List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }
7
You should probably also read Jon Skeet's Parameter Passing in C#.Randy Levy

7 Answers

17
votes

It does not act like its passed by ref.

void ChangeMe(List<int> list) {
  list = new List<int>();
  list.Add(10);
}
void ChangeMeReally(ref List<int> list) {
  list = new List<int>();
  list.Add(10);
}

Try it. Do you notice the difference?

You can only change the contents of list (or any reference type) if you pass it without a ref (because as others have said, you are passing a reference to the object on the heap and thus change the same "memory").

However you cannot change "list", "list" is a variable that points to an object of type List. You can only change "list" if you pass it by reference (to make it point somewhere else). You get a copy of the reference, which if changed, can only be observed inside your method.

7
votes

Parameters are passed by value in C# unless they are marked with the ref or out modifiers. For reference types, this means that the reference is passed by value. Therefore, in Fuss, l is referring to the same instance of List<int> as its caller. Therefore, any modifications to this instance of List<int> will be seen by the caller.

Now, if you mark the parameter l with ref or out, then the parameter is passed by reference. What this means is that in Fuss, l is an alias for storage location used as a parameter to invoke the method. To be clear:

public void Fuss(ref List<int> l)

called by

List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);

Now, in Fuss, l is an alias for list. In particular, if you assign a new instance of List<int> to l, the caller will see that new instance assigned to the variable list as well. In particular, if you say

public void Fuss(ref List<int> l) {
    l = new List<int> { 1 };
}

then the caller will now see a list with one element. But if you say

public void Fuss(List<int> l) {
    l = new List<int> { 1 };
}

and call by

List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);

then the caller will still see list as having three elements.

Clear?

3
votes

The difference between ref and non-ref for reference types like List is not whether you pass a reference (that happens always), but whether that reference can be changed. Try the following

private void Fuss(ref List<int> l)
{
    l = new List<int> { 4, 5 };
}

and you'll see the count is 2, because the function not only manipulated the original list but the reference itself.

3
votes

ByRef and ByVal only apply to value types, not to reference types, which are always passed as though they were "byref".

If you need to modify a list discreetly, use the ".ToList()" function, and you'll get a clone of your list.

Keep in mind that if your list contains reference types, your "new" list contains pointers to the same objects that your original list did.

3
votes

Lists are already reference types, so when you pass them to a method, you are passing a reference. Any Add calls will affect the list in the caller.

Passing a List<T> by ref behaves essentially like passing a double-pointer to that list. Here's an illustration:

using System;
using System.Collections.Generic;

public class Test
{
    public static void Main()
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(l);
        Console.WriteLine(l.Count); // Count will now be 5.

        FussNonRef(l);
        Console.WriteLine(l.Count); // Count will still be 5 because we 
                                    // overwrote the copy of our reference 
                                    // in FussNonRef.

        FussRef(ref l);
        Console.WriteLine(l.Count); // Count will be 2 because we changed
                                    // our reference in FussRef.
    }

    private static void Fuss(List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

    private static void FussNonRef(List<int> l)
    {
        l = new List<int>();
        l.Add(6);
        l.Add(7);
    }

    private static void FussRef(ref List<int> l)
    {
        l = new List<int>();
        l.Add(8);
        l.Add(9);
    }
}
3
votes

A variable, parameter, or field of type "List", or any other reference type, doesn't actually hold a list (or object of any other class). Instead, it will hold something like "Object ID #29115" (not such an actual string, of course, but a combination of bits which means essentially that). Elsewhere, the system will have an indexed collection of objects called the heap; if some variable of type List holds "Object ID #29115", then object #29115 in the heap will be an instance of List or some type derived therefrom.

If MyFoo is a variable of type List, a statement like 'MyFoo.Add("George")' won't actually change MyFoo; instead, it means "Examine the object ID stored in MyFoo, and invoke the "Add" method of the object stored therein. If MyFoo held "Object ID #19533" before the statement executed, it will continue to do so afterward, but Object ID #19533 will have had its Add method invoked (probably altering that object). Conversely, a statement like "MyFoo = MyBar" will make MyFoo hold the same object-id as MyBar, but won't actually do anything to the objects in question. If MyBar held "Object ID #59212" before the statement, then after the statement, MyFoo will also hold "ObjectId #59212". Nothing will have happened to object ID #19533, nor object ID#59212.

0
votes

Only primitive types like int, double etc. are passed by value.

Complex types (like list) are passed via reference (which is a passing pointer by value, to be exact).