22

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()); // output is 5
    }

    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()); // output is 5
    }

    private void Fuss(ref List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }
nawfal
  • 70,104
  • 56
  • 326
  • 368
  • 4
    You should probably also read Jon Skeet's [Parameter Passing in C#](https://jonskeet.uk/csharp/parameters.html). – Randy Levy Sep 06 '11 at 14:42

8 Answers8

17

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.

chrisaut
  • 1,076
  • 6
  • 17
10

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?

jason
  • 236,483
  • 35
  • 423
  • 525
  • I understand that. You say that in second case (without ref), if we try to change `l`, it wont get reflected in the caller. But how is it getting reflected in my problem I posed without ref? – nawfal Sep 06 '11 at 16:27
  • I think @Steven 's answer clarifies your answer for me. That is without ref I can change the contents of list but not the list itself! With ref I can do both it seems. – nawfal Sep 06 '11 at 16:29
  • @nawfal: In the second case, both the callee and caller are referring to the same instance of `List`, so of course they both see the changes. In the first case, both the callee and the caller are using the same storage location. So it's not just that they both see the same `List`, but they can both change which instance of `List` each other is looking at! – jason Sep 06 '11 at 16:41
  • I understood what you are saying. A big thanks. But I got the point clearer from FishBasketGordo and Steven. Let me mark theirs. Together you three made point clear for me. – nawfal Sep 06 '11 at 16:59
4

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);
    }
}
FishBasketGordo
  • 22,904
  • 4
  • 58
  • 91
4

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.

Wesley Long
  • 1,708
  • 10
  • 20
4

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.

supercat
  • 77,689
  • 9
  • 166
  • 211
3

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.

Tomas Vana
  • 18,317
  • 9
  • 53
  • 64
0

lets explain it Easy :)

  • "Fuss" in your first chunk of code knows that he should accept a refrence from a list type
  • "Fuss" in your second chunk of code knows that he should accept nothing! but just a refrence to "sth else" which accidently this "sth else" is the same thing which was in your first chunk of code ♥

if it seems confusing i think you should have a deep understanding of what a reference is

0

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

deha
  • 805
  • 8
  • 29
  • 1
    This is wrong. All parameters are passed by value unless marked with `ref` or `out`. – jason Sep 06 '11 at 14:49
  • You said "Only primitive types...are passed by value. Complex types...are passed via reference." As I said, **all** parameters are passed by value unless marked with `ref` or `out`, not "only primitive types.". "Complex" is not a well-defined term; I'm assuming you mean reference types. Parameters type as reference types are passed by value, unless marked with `ref` or `out`. – jason Sep 06 '11 at 14:57
  • No, I meant that method doesn't copy the object, but only its address. Think about it like in c++, where you don't have keyword 'ref'. Read again carefully. I agree with your point about "complex", refernce would be a better word. – deha Sep 06 '11 at 15:03
  • C# is not C++. "No, I meant that method doesn't copy the object, but only its address." Putting aside the sloppiness ("method doesn't copy the object"), the value is copied before being passed to the method. Always, always, always (unless marked with `ref` or `out`). For value types, the instance is the value. For reference types, the value is the reference. Note that this is not the same as the referent! – jason Sep 06 '11 at 15:09
  • You wrote the same thing as I in the parentheses. So I again don't understand what's wrong. Adding ref means passing pointer to pointer, but still by value - so according to you it's... passing by value. – deha Sep 06 '11 at 15:20
  • What's wrong? You said only primitive types are passed by value. That is false. All parameters are passed by value unless the parameter is marked with `ref` or `out`. You said the "complex types" are passed via reference. That is false (again, assuming that by "complex" you mean "reference"). All parameters are passed by value unless the parameter is marked with `ref` or `out`. – jason Sep 06 '11 at 16:10
  • I still don't understand why only my answer is "bad" according to you. – deha Sep 06 '11 at 16:26
  • provide a pointer = pass by value, provide a pointer to pointer - pass by reference. That's your logic. For me a bit strange. In programming, providing a pointer/address is called passing by reference (because we're not copying object only its address) - I was refering to it. And I don't see reason why my answer was downgraded while others not. End of topic for me here. – deha Sep 06 '11 at 16:49
  • You don't understand what "pass by reference" means. Pass by reference does not mean "passing an object reference." It means the callee sees the SAME STORAGE LOCATION as the caller. – jason Sep 06 '11 at 17:12